mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
crypto: add API for key pair generation
This adds support for RSA, DSA and EC key pair generation with a variety of possible output formats etc. PR-URL: https://github.com/nodejs/node/pull/22660 Fixes: https://github.com/nodejs/node/issues/15116 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
df9abb638d
commit
8c502f54ce
@ -1673,6 +1673,116 @@ Use [`crypto.getHashes()`][] to obtain an array of names of the available
|
||||
signing algorithms. Optional `options` argument controls the
|
||||
`stream.Writable` behavior.
|
||||
|
||||
### crypto.generateKeyPair(type, options, callback)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
|
||||
* `options`: {Object}
|
||||
- `modulusLength`: {number} Key size in bits (RSA, DSA).
|
||||
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
|
||||
- `divisorLength`: {number} Size of `q` in bits (DSA).
|
||||
- `namedCurve`: {string} Name of the curve to use (EC).
|
||||
- `publicKeyEncoding`: {Object}
|
||||
- `type`: {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
|
||||
- `format`: {string} Must be `'pem'` or `'der'`.
|
||||
- `privateKeyEncoding`: {Object}
|
||||
- `type`: {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or
|
||||
`'sec1'` (EC only).
|
||||
- `format`: {string} Must be `'pem'` or `'der'`.
|
||||
- `cipher`: {string} If specified, the private key will be encrypted with
|
||||
the given `cipher` and `passphrase` using PKCS#5 v2.0 password based
|
||||
encryption.
|
||||
- `passphrase`: {string} The passphrase to use for encryption, see `cipher`.
|
||||
* `callback`: {Function}
|
||||
- `err`: {Error}
|
||||
- `publicKey`: {string|Buffer}
|
||||
- `privateKey`: {string|Buffer}
|
||||
|
||||
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
|
||||
are currently supported.
|
||||
|
||||
It is recommended to encode public keys as `'spki'` and private keys as
|
||||
`'pkcs8'` with encryption:
|
||||
|
||||
```js
|
||||
const { generateKeyPair } = require('crypto');
|
||||
generateKeyPair('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: 'top secret'
|
||||
}
|
||||
}, (err, publicKey, privateKey) => {
|
||||
// Handle errors and use the generated key pair.
|
||||
});
|
||||
```
|
||||
|
||||
On completion, `callback` will be called with `err` set to `undefined` and
|
||||
`publicKey` / `privateKey` representing the generated key pair. When PEM
|
||||
encoding was selected, the result will be a string, otherwise it will be a
|
||||
buffer containing the data encoded as DER. Note that Node.js itself does not
|
||||
accept DER, it is supported for interoperability with other libraries such as
|
||||
WebCrypto only.
|
||||
|
||||
### crypto.generateKeyPairSync(type, options)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
|
||||
* `options`: {Object}
|
||||
- `modulusLength`: {number} Key size in bits (RSA, DSA).
|
||||
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
|
||||
- `divisorLength`: {number} Size of `q` in bits (DSA).
|
||||
- `namedCurve`: {string} Name of the curve to use (EC).
|
||||
- `publicKeyEncoding`: {Object}
|
||||
- `type`: {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
|
||||
- `format`: {string} Must be `'pem'` or `'der'`.
|
||||
- `privateKeyEncoding`: {Object}
|
||||
- `type`: {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or
|
||||
`'sec1'` (EC only).
|
||||
- `format`: {string} Must be `'pem'` or `'der'`.
|
||||
- `cipher`: {string} If specified, the private key will be encrypted with
|
||||
the given `cipher` and `passphrase` using PKCS#5 v2.0 password based
|
||||
encryption.
|
||||
- `passphrase`: {string} The passphrase to use for encryption, see `cipher`.
|
||||
* Returns: {Object}
|
||||
- `publicKey`: {string|Buffer}
|
||||
- `privateKey`: {string|Buffer}
|
||||
|
||||
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
|
||||
are currently supported.
|
||||
|
||||
It is recommended to encode public keys as `'spki'` and private keys as
|
||||
`'pkcs8'` with encryption:
|
||||
|
||||
```js
|
||||
const { generateKeyPairSync } = require('crypto');
|
||||
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: 'top secret'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The return value `{ publicKey, privateKey }` represents the generated key pair.
|
||||
When PEM encoding was selected, the respective key will be a string, otherwise
|
||||
it will be a buffer containing the data encoded as DER.
|
||||
|
||||
### crypto.getCiphers()
|
||||
<!-- YAML
|
||||
added: v0.9.3
|
||||
|
@ -729,6 +729,11 @@ be called no more than one time per instance of a `Hash` object.
|
||||
|
||||
[`hash.update()`][] failed for any reason. This should rarely, if ever, happen.
|
||||
|
||||
<a id="ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS"></a>
|
||||
### ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS
|
||||
|
||||
The selected public or private key encoding is incompatible with other options.
|
||||
|
||||
<a id="ERR_CRYPTO_INVALID_DIGEST"></a>
|
||||
### ERR_CRYPTO_INVALID_DIGEST
|
||||
|
||||
|
@ -54,6 +54,10 @@ const {
|
||||
scrypt,
|
||||
scryptSync
|
||||
} = require('internal/crypto/scrypt');
|
||||
const {
|
||||
generateKeyPair,
|
||||
generateKeyPairSync
|
||||
} = require('internal/crypto/keygen');
|
||||
const {
|
||||
DiffieHellman,
|
||||
DiffieHellmanGroup,
|
||||
@ -152,6 +156,8 @@ module.exports = exports = {
|
||||
getHashes,
|
||||
pbkdf2,
|
||||
pbkdf2Sync,
|
||||
generateKeyPair,
|
||||
generateKeyPairSync,
|
||||
privateDecrypt,
|
||||
privateEncrypt,
|
||||
publicDecrypt,
|
||||
|
238
lib/internal/crypto/keygen.js
Normal file
238
lib/internal/crypto/keygen.js
Normal file
@ -0,0 +1,238 @@
|
||||
'use strict';
|
||||
|
||||
const { internalBinding } = require('internal/bootstrap/loaders');
|
||||
const { AsyncWrap, Providers } = internalBinding('async_wrap');
|
||||
const {
|
||||
generateKeyPairRSA,
|
||||
generateKeyPairDSA,
|
||||
generateKeyPairEC,
|
||||
OPENSSL_EC_NAMED_CURVE,
|
||||
OPENSSL_EC_EXPLICIT_CURVE,
|
||||
PK_ENCODING_PKCS1,
|
||||
PK_ENCODING_PKCS8,
|
||||
PK_ENCODING_SPKI,
|
||||
PK_ENCODING_SEC1,
|
||||
PK_FORMAT_DER,
|
||||
PK_FORMAT_PEM
|
||||
} = internalBinding('crypto');
|
||||
const { isUint32 } = require('internal/validators');
|
||||
const {
|
||||
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_INVALID_CALLBACK,
|
||||
ERR_INVALID_OPT_VALUE
|
||||
} = require('internal/errors').codes;
|
||||
|
||||
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();
|
||||
|
||||
const wrap = new AsyncWrap(Providers.KEYPAIRGENREQUEST);
|
||||
wrap.ondone = (ex, pubkey, privkey) => {
|
||||
if (ex) return callback.call(wrap, ex);
|
||||
callback.call(wrap, null, pubkey, privkey);
|
||||
};
|
||||
|
||||
handleError(impl, wrap);
|
||||
}
|
||||
|
||||
function generateKeyPairSync(type, options) {
|
||||
const impl = check(type, options);
|
||||
return handleError(impl);
|
||||
}
|
||||
|
||||
function handleError(impl, wrap) {
|
||||
const ret = impl(wrap);
|
||||
if (ret === undefined)
|
||||
return; // async
|
||||
|
||||
const [err, publicKey, privateKey] = ret;
|
||||
if (err !== undefined)
|
||||
throw err;
|
||||
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
|
||||
function parseKeyEncoding(keyType, options) {
|
||||
const { publicKeyEncoding, privateKeyEncoding } = options;
|
||||
|
||||
if (publicKeyEncoding == null || typeof publicKeyEncoding !== 'object')
|
||||
throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding', publicKeyEncoding);
|
||||
|
||||
const { format: strPublicFormat, type: strPublicType } = publicKeyEncoding;
|
||||
|
||||
let publicType;
|
||||
if (strPublicType === 'pkcs1') {
|
||||
if (keyType !== 'rsa') {
|
||||
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
||||
strPublicType, 'can only be used for RSA keys');
|
||||
}
|
||||
publicType = PK_ENCODING_PKCS1;
|
||||
} else if (strPublicType === 'spki') {
|
||||
publicType = PK_ENCODING_SPKI;
|
||||
} else {
|
||||
throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.type', strPublicType);
|
||||
}
|
||||
|
||||
let publicFormat;
|
||||
if (strPublicFormat === 'der') {
|
||||
publicFormat = PK_FORMAT_DER;
|
||||
} else if (strPublicFormat === 'pem') {
|
||||
publicFormat = PK_FORMAT_PEM;
|
||||
} else {
|
||||
throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.format',
|
||||
strPublicFormat);
|
||||
}
|
||||
|
||||
if (privateKeyEncoding == null || typeof privateKeyEncoding !== 'object')
|
||||
throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding', privateKeyEncoding);
|
||||
|
||||
const {
|
||||
cipher,
|
||||
passphrase,
|
||||
format: strPrivateFormat,
|
||||
type: strPrivateType
|
||||
} = privateKeyEncoding;
|
||||
|
||||
let privateType;
|
||||
if (strPrivateType === 'pkcs1') {
|
||||
if (keyType !== 'rsa') {
|
||||
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
||||
strPrivateType, 'can only be used for RSA keys');
|
||||
}
|
||||
privateType = PK_ENCODING_PKCS1;
|
||||
} else if (strPrivateType === 'pkcs8') {
|
||||
privateType = PK_ENCODING_PKCS8;
|
||||
} else if (strPrivateType === 'sec1') {
|
||||
if (keyType !== 'ec') {
|
||||
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
||||
strPrivateType, 'can only be used for EC keys');
|
||||
}
|
||||
privateType = PK_ENCODING_SEC1;
|
||||
} else {
|
||||
throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.type', strPrivateType);
|
||||
}
|
||||
|
||||
let privateFormat;
|
||||
if (strPrivateFormat === 'der') {
|
||||
privateFormat = PK_FORMAT_DER;
|
||||
} else if (strPrivateFormat === 'pem') {
|
||||
privateFormat = PK_FORMAT_PEM;
|
||||
} else {
|
||||
throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.format',
|
||||
strPrivateFormat);
|
||||
}
|
||||
|
||||
if (cipher != null) {
|
||||
if (typeof cipher !== 'string')
|
||||
throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.cipher', cipher);
|
||||
if (privateType !== PK_ENCODING_PKCS8) {
|
||||
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
||||
strPrivateType, 'does not support encryption');
|
||||
}
|
||||
if (typeof passphrase !== 'string') {
|
||||
throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.passphrase',
|
||||
passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cipher, passphrase, publicType, publicFormat, privateType, privateFormat
|
||||
};
|
||||
}
|
||||
|
||||
function check(type, options, callback) {
|
||||
if (typeof type !== 'string')
|
||||
throw new ERR_INVALID_ARG_TYPE('type', 'string', type);
|
||||
if (options == null || typeof options !== 'object')
|
||||
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
|
||||
|
||||
// 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;
|
||||
|
||||
let impl;
|
||||
switch (type) {
|
||||
case 'rsa':
|
||||
{
|
||||
const { modulusLength } = options;
|
||||
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);
|
||||
}
|
||||
|
||||
impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
|
||||
publicType, publicFormat,
|
||||
privateType, privateFormat,
|
||||
cipher, passphrase, wrap);
|
||||
}
|
||||
break;
|
||||
case 'dsa':
|
||||
{
|
||||
const { modulusLength } = options;
|
||||
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,
|
||||
publicType, publicFormat,
|
||||
privateType, privateFormat,
|
||||
cipher, passphrase, wrap);
|
||||
}
|
||||
break;
|
||||
case 'ec':
|
||||
{
|
||||
const { namedCurve } = options;
|
||||
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,
|
||||
publicType, publicFormat,
|
||||
privateType, privateFormat,
|
||||
cipher, passphrase, wrap);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ERR_INVALID_ARG_VALUE('type', type,
|
||||
"must be one of 'rsa', 'dsa', 'ec'");
|
||||
}
|
||||
|
||||
({
|
||||
cipher,
|
||||
passphrase,
|
||||
publicType,
|
||||
publicFormat,
|
||||
privateType,
|
||||
privateFormat
|
||||
} = parseKeyEncoding(type, options));
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
module.exports = { generateKeyPair, generateKeyPairSync };
|
@ -509,6 +509,8 @@ E('ERR_CRYPTO_HASH_DIGEST_NO_UTF16', 'hash.digest() does not support UTF-16',
|
||||
Error);
|
||||
E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called', Error);
|
||||
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error);
|
||||
E('ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', 'The selected key encoding %s %s.',
|
||||
Error);
|
||||
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
|
||||
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
|
||||
E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error);
|
||||
|
1
node.gyp
1
node.gyp
@ -97,6 +97,7 @@
|
||||
'lib/internal/crypto/cipher.js',
|
||||
'lib/internal/crypto/diffiehellman.js',
|
||||
'lib/internal/crypto/hash.js',
|
||||
'lib/internal/crypto/keygen.js',
|
||||
'lib/internal/crypto/pbkdf2.js',
|
||||
'lib/internal/crypto/random.js',
|
||||
'lib/internal/crypto/scrypt.js',
|
||||
|
@ -73,6 +73,7 @@ namespace node {
|
||||
#if HAVE_OPENSSL
|
||||
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
|
||||
V(PBKDF2REQUEST) \
|
||||
V(KEYPAIRGENREQUEST) \
|
||||
V(RANDOMBYTESREQUEST) \
|
||||
V(SCRYPTREQUEST) \
|
||||
V(TLSWRAP)
|
||||
|
@ -88,6 +88,7 @@ using v8::SideEffectType;
|
||||
using v8::Signature;
|
||||
using v8::String;
|
||||
using v8::Uint32;
|
||||
using v8::Undefined;
|
||||
using v8::Value;
|
||||
|
||||
|
||||
@ -4832,6 +4833,453 @@ void Scrypt(const FunctionCallbackInfo<Value>& args) {
|
||||
#endif // OPENSSL_NO_SCRYPT
|
||||
|
||||
|
||||
class KeyPairGenerationConfig {
|
||||
public:
|
||||
virtual EVPKeyCtxPointer Setup() = 0;
|
||||
virtual bool Configure(const EVPKeyCtxPointer& ctx) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class RSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
||||
public:
|
||||
RSAKeyPairGenerationConfig(unsigned int modulus_bits, unsigned int exponent)
|
||||
: modulus_bits_(modulus_bits), exponent_(exponent) {}
|
||||
|
||||
EVPKeyCtxPointer Setup() override {
|
||||
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr));
|
||||
}
|
||||
|
||||
bool Configure(const EVPKeyCtxPointer& ctx) override {
|
||||
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), modulus_bits_) <= 0)
|
||||
return false;
|
||||
|
||||
// 0x10001 is the default RSA exponent.
|
||||
if (exponent_ != 0x10001) {
|
||||
BignumPointer bn(BN_new());
|
||||
CHECK_NOT_NULL(bn.get());
|
||||
CHECK(BN_set_word(bn.get(), exponent_));
|
||||
if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx.get(), bn.get()) <= 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const unsigned int modulus_bits_;
|
||||
const unsigned int exponent_;
|
||||
};
|
||||
|
||||
class DSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
||||
public:
|
||||
DSAKeyPairGenerationConfig(unsigned int modulus_bits, int divisor_bits)
|
||||
: modulus_bits_(modulus_bits), divisor_bits_(divisor_bits) {}
|
||||
|
||||
EVPKeyCtxPointer Setup() override {
|
||||
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, nullptr));
|
||||
if (!param_ctx)
|
||||
return nullptr;
|
||||
|
||||
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
|
||||
return nullptr;
|
||||
|
||||
if (EVP_PKEY_CTX_set_dsa_paramgen_bits(param_ctx.get(), modulus_bits_) <= 0)
|
||||
return nullptr;
|
||||
|
||||
if (divisor_bits_ != -1) {
|
||||
if (EVP_PKEY_CTX_ctrl(param_ctx.get(), EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN,
|
||||
EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, divisor_bits_,
|
||||
nullptr) <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
EVP_PKEY* params = nullptr;
|
||||
if (EVP_PKEY_paramgen(param_ctx.get(), ¶ms) <= 0)
|
||||
return nullptr;
|
||||
param_ctx.reset();
|
||||
|
||||
EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(params, nullptr));
|
||||
EVP_PKEY_free(params);
|
||||
return key_ctx;
|
||||
}
|
||||
|
||||
private:
|
||||
const unsigned int modulus_bits_;
|
||||
const int divisor_bits_;
|
||||
};
|
||||
|
||||
class ECKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
||||
public:
|
||||
ECKeyPairGenerationConfig(int curve_nid, int param_encoding)
|
||||
: curve_nid_(curve_nid), param_encoding_(param_encoding) {}
|
||||
|
||||
EVPKeyCtxPointer Setup() override {
|
||||
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr));
|
||||
if (!param_ctx)
|
||||
return nullptr;
|
||||
|
||||
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
|
||||
return nullptr;
|
||||
|
||||
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(param_ctx.get(),
|
||||
curve_nid_) <= 0)
|
||||
return nullptr;
|
||||
|
||||
if (EVP_PKEY_CTX_set_ec_param_enc(param_ctx.get(), param_encoding_) <= 0)
|
||||
return nullptr;
|
||||
|
||||
EVP_PKEY* params = nullptr;
|
||||
if (EVP_PKEY_paramgen(param_ctx.get(), ¶ms) <= 0)
|
||||
return nullptr;
|
||||
param_ctx.reset();
|
||||
|
||||
EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(params, nullptr));
|
||||
EVP_PKEY_free(params);
|
||||
return key_ctx;
|
||||
}
|
||||
|
||||
private:
|
||||
const int curve_nid_;
|
||||
const int param_encoding_;
|
||||
};
|
||||
|
||||
enum PKEncodingType {
|
||||
// RSAPublicKey / RSAPrivateKey according to PKCS#1.
|
||||
PK_ENCODING_PKCS1,
|
||||
// PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
|
||||
PK_ENCODING_PKCS8,
|
||||
// SubjectPublicKeyInfo according to X.509.
|
||||
PK_ENCODING_SPKI,
|
||||
// ECPrivateKey according to SEC1.
|
||||
PK_ENCODING_SEC1
|
||||
};
|
||||
|
||||
enum PKFormatType {
|
||||
PK_FORMAT_DER,
|
||||
PK_FORMAT_PEM
|
||||
};
|
||||
|
||||
struct KeyPairEncodingConfig {
|
||||
PKEncodingType type_;
|
||||
PKFormatType format_;
|
||||
};
|
||||
|
||||
typedef KeyPairEncodingConfig PublicKeyEncodingConfig;
|
||||
|
||||
struct PrivateKeyEncodingConfig : public KeyPairEncodingConfig {
|
||||
const EVP_CIPHER* cipher_;
|
||||
// This char* will be passed to OPENSSL_clear_free.
|
||||
std::shared_ptr<char> passphrase_;
|
||||
unsigned int passphrase_length_;
|
||||
};
|
||||
|
||||
class GenerateKeyPairJob : public CryptoJob {
|
||||
public:
|
||||
GenerateKeyPairJob(Environment* env,
|
||||
std::unique_ptr<KeyPairGenerationConfig> config,
|
||||
PublicKeyEncodingConfig public_key_encoding,
|
||||
PrivateKeyEncodingConfig private_key_encoding)
|
||||
: CryptoJob(env),
|
||||
config_(std::move(config)),
|
||||
public_key_encoding_(public_key_encoding),
|
||||
private_key_encoding_(private_key_encoding),
|
||||
pkey_(nullptr) {}
|
||||
|
||||
inline void DoThreadPoolWork() override {
|
||||
if (!GenerateKey())
|
||||
errors_.Capture();
|
||||
}
|
||||
|
||||
inline bool GenerateKey() {
|
||||
// Make sure that the CSPRNG is properly seeded so the results are secure.
|
||||
CheckEntropy();
|
||||
|
||||
// Create the key generation context.
|
||||
EVPKeyCtxPointer ctx = config_->Setup();
|
||||
if (!ctx)
|
||||
return false;
|
||||
|
||||
// Initialize key generation.
|
||||
if (EVP_PKEY_keygen_init(ctx.get()) <= 0)
|
||||
return false;
|
||||
|
||||
// Configure key generation.
|
||||
if (!config_->Configure(ctx))
|
||||
return false;
|
||||
|
||||
// Generate the key.
|
||||
EVP_PKEY* pkey = nullptr;
|
||||
if (EVP_PKEY_keygen(ctx.get(), &pkey) != 1)
|
||||
return false;
|
||||
pkey_.reset(pkey);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void AfterThreadPoolWork() override {
|
||||
Local<Value> args[3];
|
||||
ToResult(&args[0], &args[1], &args[2]);
|
||||
async_wrap->MakeCallback(env->ondone_string(), 3, args);
|
||||
}
|
||||
|
||||
inline void ToResult(Local<Value>* err,
|
||||
Local<Value>* pubkey,
|
||||
Local<Value>* privkey) {
|
||||
if (pkey_ && EncodeKeys(pubkey, privkey)) {
|
||||
CHECK(errors_.empty());
|
||||
*err = Undefined(env->isolate());
|
||||
} else {
|
||||
if (errors_.empty())
|
||||
errors_.Capture();
|
||||
CHECK(!errors_.empty());
|
||||
*err = errors_.ToException(env);
|
||||
*pubkey = Undefined(env->isolate());
|
||||
*privkey = Undefined(env->isolate());
|
||||
}
|
||||
}
|
||||
|
||||
inline bool EncodeKeys(Local<Value>* pubkey, Local<Value>* privkey) {
|
||||
EVP_PKEY* pkey = pkey_.get();
|
||||
BIOPointer bio(BIO_new(BIO_s_mem()));
|
||||
CHECK(bio);
|
||||
|
||||
// Encode the public key.
|
||||
if (public_key_encoding_.type_ == PK_ENCODING_PKCS1) {
|
||||
// PKCS#1 is only valid for RSA keys.
|
||||
CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
|
||||
RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
|
||||
if (public_key_encoding_.format_ == PK_FORMAT_PEM) {
|
||||
// Encode PKCS#1 as PEM.
|
||||
if (PEM_write_bio_RSAPublicKey(bio.get(), rsa.get()) != 1)
|
||||
return false;
|
||||
} else {
|
||||
// Encode PKCS#1 as DER.
|
||||
CHECK_EQ(public_key_encoding_.format_, PK_FORMAT_DER);
|
||||
if (i2d_RSAPublicKey_bio(bio.get(), rsa.get()) != 1)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
CHECK_EQ(public_key_encoding_.type_, PK_ENCODING_SPKI);
|
||||
if (public_key_encoding_.format_ == PK_FORMAT_PEM) {
|
||||
// Encode SPKI as PEM.
|
||||
if (PEM_write_bio_PUBKEY(bio.get(), pkey) != 1)
|
||||
return false;
|
||||
} else {
|
||||
// Encode SPKI as DER.
|
||||
CHECK_EQ(public_key_encoding_.format_, PK_FORMAT_DER);
|
||||
if (i2d_PUBKEY_bio(bio.get(), pkey) != 1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the contents of the BIO to a JavaScript object.
|
||||
BIOToStringOrBuffer(bio.get(), public_key_encoding_.format_, pubkey);
|
||||
USE(BIO_reset(bio.get()));
|
||||
|
||||
// Now do the same for the private key (which is a bit more difficult).
|
||||
if (private_key_encoding_.type_ == PK_ENCODING_PKCS1) {
|
||||
// PKCS#1 is only permitted for RSA keys and without encryption.
|
||||
CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
|
||||
CHECK_NULL(private_key_encoding_.cipher_);
|
||||
|
||||
RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
|
||||
if (private_key_encoding_.format_ == PK_FORMAT_PEM) {
|
||||
// Encode PKCS#1 as PEM.
|
||||
if (PEM_write_bio_RSAPrivateKey(bio.get(), rsa.get(),
|
||||
nullptr, nullptr, 0,
|
||||
nullptr, nullptr) != 1)
|
||||
return false;
|
||||
} else {
|
||||
// Encode PKCS#1 as DER.
|
||||
CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER);
|
||||
if (i2d_RSAPrivateKey_bio(bio.get(), rsa.get()) != 1)
|
||||
return false;
|
||||
}
|
||||
} else if (private_key_encoding_.type_ == PK_ENCODING_PKCS8) {
|
||||
if (private_key_encoding_.format_ == PK_FORMAT_PEM) {
|
||||
// Encode PKCS#8 as PEM.
|
||||
if (PEM_write_bio_PKCS8PrivateKey(
|
||||
bio.get(), pkey,
|
||||
private_key_encoding_.cipher_,
|
||||
private_key_encoding_.passphrase_.get(),
|
||||
private_key_encoding_.passphrase_length_,
|
||||
nullptr, nullptr) != 1)
|
||||
return false;
|
||||
} else {
|
||||
// Encode PKCS#8 as DER.
|
||||
CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER);
|
||||
if (i2d_PKCS8PrivateKey_bio(
|
||||
bio.get(), pkey,
|
||||
private_key_encoding_.cipher_,
|
||||
private_key_encoding_.passphrase_.get(),
|
||||
private_key_encoding_.passphrase_length_,
|
||||
nullptr, nullptr) != 1)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
CHECK_EQ(private_key_encoding_.type_, PK_ENCODING_SEC1);
|
||||
|
||||
// SEC1 is only permitted for EC keys and without encryption.
|
||||
CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_EC);
|
||||
CHECK_NULL(private_key_encoding_.cipher_);
|
||||
|
||||
ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey));
|
||||
if (private_key_encoding_.format_ == PK_FORMAT_PEM) {
|
||||
// Encode SEC1 as PEM.
|
||||
if (PEM_write_bio_ECPrivateKey(bio.get(), ec_key.get(),
|
||||
nullptr, nullptr, 0,
|
||||
nullptr, nullptr) != 1)
|
||||
return false;
|
||||
} else {
|
||||
// Encode SEC1 as DER.
|
||||
CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER);
|
||||
if (i2d_ECPrivateKey_bio(bio.get(), ec_key.get()) != 1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BIOToStringOrBuffer(bio.get(), private_key_encoding_.format_, privkey);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void BIOToStringOrBuffer(BIO* bio, PKFormatType format,
|
||||
Local<Value>* out) const {
|
||||
BUF_MEM* bptr;
|
||||
BIO_get_mem_ptr(bio, &bptr);
|
||||
if (format == PK_FORMAT_PEM) {
|
||||
// PEM is an ASCII format, so we will return it as a string.
|
||||
*out = String::NewFromUtf8(env->isolate(), bptr->data,
|
||||
NewStringType::kNormal,
|
||||
bptr->length).ToLocalChecked();
|
||||
} else {
|
||||
CHECK_EQ(format, PK_FORMAT_DER);
|
||||
// DER is binary, return it as a buffer.
|
||||
*out = Buffer::Copy(env, bptr->data, bptr->length).ToLocalChecked();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoErrorVector errors_;
|
||||
std::unique_ptr<KeyPairGenerationConfig> config_;
|
||||
PublicKeyEncodingConfig public_key_encoding_;
|
||||
PrivateKeyEncodingConfig private_key_encoding_;
|
||||
EVPKeyPointer pkey_;
|
||||
};
|
||||
|
||||
void GenerateKeyPair(const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int n_opts,
|
||||
std::unique_ptr<KeyPairGenerationConfig> config) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
PublicKeyEncodingConfig public_key_encoding;
|
||||
PrivateKeyEncodingConfig private_key_encoding;
|
||||
|
||||
// Public key encoding: type (int) + pem (bool)
|
||||
CHECK(args[n_opts]->IsInt32());
|
||||
public_key_encoding.type_ = static_cast<PKEncodingType>(
|
||||
args[n_opts].As<Int32>()->Value());
|
||||
CHECK(args[n_opts + 1]->IsInt32());
|
||||
public_key_encoding.format_ = static_cast<PKFormatType>(
|
||||
args[n_opts + 1].As<Int32>()->Value());
|
||||
|
||||
// Private key encoding: type (int) + pem (bool) + cipher (optional, string) +
|
||||
// passphrase (optional, string)
|
||||
CHECK(args[n_opts + 2]->IsInt32());
|
||||
private_key_encoding.type_ = static_cast<PKEncodingType>(
|
||||
args[n_opts + 2].As<Int32>()->Value());
|
||||
CHECK(args[n_opts + 1]->IsInt32());
|
||||
private_key_encoding.format_ = static_cast<PKFormatType>(
|
||||
args[n_opts + 3].As<Int32>()->Value());
|
||||
if (args[n_opts + 4]->IsString()) {
|
||||
String::Utf8Value cipher_name(env->isolate(),
|
||||
args[n_opts + 4].As<String>());
|
||||
private_key_encoding.cipher_ = EVP_get_cipherbyname(*cipher_name);
|
||||
if (private_key_encoding.cipher_ == nullptr)
|
||||
return env->ThrowError("Unknown cipher");
|
||||
|
||||
// We need to take ownership of the string and want to avoid creating an
|
||||
// unnecessary copy in memory, that's why we are not using String::Utf8Value
|
||||
// here.
|
||||
CHECK(args[n_opts + 5]->IsString());
|
||||
Local<String> passphrase = args[n_opts + 5].As<String>();
|
||||
int len = passphrase->Utf8Length(env->isolate());
|
||||
private_key_encoding.passphrase_length_ = len;
|
||||
void* mem = OPENSSL_malloc(private_key_encoding.passphrase_length_ + 1);
|
||||
CHECK_NOT_NULL(mem);
|
||||
private_key_encoding.passphrase_.reset(static_cast<char*>(mem),
|
||||
[len](char* p) {
|
||||
OPENSSL_clear_free(p, len);
|
||||
});
|
||||
passphrase->WriteUtf8(env->isolate(),
|
||||
private_key_encoding.passphrase_.get());
|
||||
} else {
|
||||
CHECK(args[n_opts + 5]->IsNullOrUndefined());
|
||||
private_key_encoding.cipher_ = nullptr;
|
||||
private_key_encoding.passphrase_length_ = 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<GenerateKeyPairJob> job(
|
||||
new GenerateKeyPairJob(env, std::move(config), public_key_encoding,
|
||||
private_key_encoding));
|
||||
if (args[n_opts + 6]->IsObject())
|
||||
return GenerateKeyPairJob::Run(std::move(job), args[n_opts + 6]);
|
||||
env->PrintSyncTrace();
|
||||
job->DoThreadPoolWork();
|
||||
Local<Value> err, pubkey, privkey;
|
||||
job->ToResult(&err, &pubkey, &privkey);
|
||||
|
||||
bool (*IsNotTrue)(Maybe<bool>) = [](Maybe<bool> maybe) {
|
||||
return maybe.IsNothing() || !maybe.ToChecked();
|
||||
};
|
||||
Local<Array> ret = Array::New(env->isolate(), 3);
|
||||
if (IsNotTrue(ret->Set(env->context(), 0, err)) ||
|
||||
IsNotTrue(ret->Set(env->context(), 1, pubkey)) ||
|
||||
IsNotTrue(ret->Set(env->context(), 2, privkey)))
|
||||
return;
|
||||
args.GetReturnValue().Set(ret);
|
||||
}
|
||||
|
||||
void GenerateKeyPairRSA(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args[0]->IsUint32());
|
||||
const uint32_t modulus_bits = args[0].As<Uint32>()->Value();
|
||||
CHECK(args[1]->IsUint32());
|
||||
const uint32_t exponent = args[1].As<Uint32>()->Value();
|
||||
std::unique_ptr<KeyPairGenerationConfig> config(
|
||||
new RSAKeyPairGenerationConfig(modulus_bits, exponent));
|
||||
GenerateKeyPair(args, 2, std::move(config));
|
||||
}
|
||||
|
||||
void GenerateKeyPairDSA(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args[0]->IsUint32());
|
||||
const uint32_t modulus_bits = args[0].As<Uint32>()->Value();
|
||||
CHECK(args[1]->IsInt32());
|
||||
const int32_t divisor_bits = args[1].As<Int32>()->Value();
|
||||
std::unique_ptr<KeyPairGenerationConfig> config(
|
||||
new DSAKeyPairGenerationConfig(modulus_bits, divisor_bits));
|
||||
GenerateKeyPair(args, 2, std::move(config));
|
||||
}
|
||||
|
||||
void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args[0]->IsString());
|
||||
String::Utf8Value curve_name(args.GetIsolate(), args[0].As<String>());
|
||||
int curve_nid = EC_curve_nist2nid(*curve_name);
|
||||
if (curve_nid == NID_undef)
|
||||
curve_nid = OBJ_sn2nid(*curve_name);
|
||||
// TODO(tniessen): Should we also support OBJ_ln2nid? (Other APIs don't.)
|
||||
if (curve_nid == NID_undef) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
return env->ThrowTypeError("Invalid ECDH curve name");
|
||||
}
|
||||
CHECK(args[1]->IsUint32());
|
||||
const uint32_t param_encoding = args[1].As<Int32>()->Value();
|
||||
CHECK(param_encoding == OPENSSL_EC_NAMED_CURVE ||
|
||||
param_encoding == OPENSSL_EC_EXPLICIT_CURVE);
|
||||
std::unique_ptr<KeyPairGenerationConfig> config(
|
||||
new ECKeyPairGenerationConfig(curve_nid, param_encoding));
|
||||
GenerateKeyPair(args, 2, std::move(config));
|
||||
}
|
||||
|
||||
|
||||
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
@ -5242,6 +5690,17 @@ void Initialize(Local<Object> target,
|
||||
#endif
|
||||
|
||||
env->SetMethod(target, "pbkdf2", PBKDF2);
|
||||
env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
|
||||
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
|
||||
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
|
||||
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
|
||||
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
|
||||
NODE_DEFINE_CONSTANT(target, PK_ENCODING_PKCS1);
|
||||
NODE_DEFINE_CONSTANT(target, PK_ENCODING_PKCS8);
|
||||
NODE_DEFINE_CONSTANT(target, PK_ENCODING_SPKI);
|
||||
NODE_DEFINE_CONSTANT(target, PK_ENCODING_SEC1);
|
||||
NODE_DEFINE_CONSTANT(target, PK_FORMAT_DER);
|
||||
NODE_DEFINE_CONSTANT(target, PK_FORMAT_PEM);
|
||||
env->SetMethod(target, "randomBytes", RandomBytes);
|
||||
env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
|
||||
env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
|
||||
|
634
test/parallel/test-crypto-keygen.js
Normal file
634
test/parallel/test-crypto-keygen.js
Normal file
@ -0,0 +1,634 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
createSign,
|
||||
createVerify,
|
||||
generateKeyPair,
|
||||
generateKeyPairSync,
|
||||
publicEncrypt,
|
||||
privateDecrypt
|
||||
} = require('crypto');
|
||||
|
||||
// Asserts that the size of the given key (in chars or bytes) is within 10% of
|
||||
// the expected size.
|
||||
function assertApproximateSize(key, expectedSize) {
|
||||
const u = typeof key === 'string' ? 'chars' : 'bytes';
|
||||
const min = Math.floor(0.9 * expectedSize);
|
||||
const max = Math.ceil(1.1 * expectedSize);
|
||||
assert(key.length >= min,
|
||||
`Key (${key.length} ${u}) is shorter than expected (${min} ${u})`);
|
||||
assert(key.length <= max,
|
||||
`Key (${key.length} ${u}) is longer than expected (${max} ${u})`);
|
||||
}
|
||||
|
||||
// Tests that a key pair can be used for encryption / decryption.
|
||||
function testEncryptDecrypt(publicKey, privateKey) {
|
||||
const message = 'Hello Node.js world!';
|
||||
const plaintext = Buffer.from(message, 'utf8');
|
||||
const ciphertext = publicEncrypt(publicKey, plaintext);
|
||||
const received = privateDecrypt(privateKey, ciphertext);
|
||||
assert.strictEqual(received.toString('utf8'), message);
|
||||
}
|
||||
|
||||
// Tests that a key pair can be used for signing / verification.
|
||||
function testSignVerify(publicKey, privateKey) {
|
||||
const message = 'Hello Node.js world!';
|
||||
const signature = createSign('SHA256').update(message)
|
||||
.sign(privateKey, 'hex');
|
||||
const okay = createVerify('SHA256').update(message)
|
||||
.verify(publicKey, signature, 'hex');
|
||||
assert(okay);
|
||||
}
|
||||
|
||||
// Constructs a regular expression for a PEM-encoded key with the given label.
|
||||
function getRegExpForPEM(label) {
|
||||
const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`;
|
||||
const body = '([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}';
|
||||
const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`;
|
||||
return new RegExp(`^${head}\n${body}\n${end}\n$`);
|
||||
}
|
||||
|
||||
const pkcs1PubExp = getRegExpForPEM('RSA PUBLIC KEY');
|
||||
const pkcs1PrivExp = getRegExpForPEM('RSA PRIVATE KEY');
|
||||
const spkiExp = getRegExpForPEM('PUBLIC KEY');
|
||||
const pkcs8Exp = getRegExpForPEM('PRIVATE KEY');
|
||||
const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY');
|
||||
const sec1Exp = getRegExpForPEM('EC PRIVATE KEY');
|
||||
|
||||
// Since our own APIs only accept PEM, not DER, we need to convert DER to PEM
|
||||
// for testing.
|
||||
function convertDERToPEM(label, der) {
|
||||
const base64 = der.toString('base64');
|
||||
const lines = [];
|
||||
let i = 0;
|
||||
while (i < base64.length) {
|
||||
const n = Math.min(base64.length - i, 64);
|
||||
lines.push(base64.substr(i, n));
|
||||
i += n;
|
||||
}
|
||||
const body = lines.join('\n');
|
||||
const r = `-----BEGIN ${label}-----\n${body}\n-----END ${label}-----\n`;
|
||||
assert(getRegExpForPEM(label).test(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
{
|
||||
// To make the test faster, we will only test sync key generation once and
|
||||
// with a relatively small key.
|
||||
const ret = generateKeyPairSync('rsa', {
|
||||
publicExponent: 0x10001,
|
||||
modulusLength: 1024,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(Object.keys(ret).length, 2);
|
||||
const { publicKey, privateKey } = ret;
|
||||
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert(pkcs1PubExp.test(publicKey));
|
||||
assertApproximateSize(publicKey, 272);
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert(pkcs8Exp.test(privateKey));
|
||||
assertApproximateSize(privateKey, 912);
|
||||
|
||||
testEncryptDecrypt(publicKey, privateKey);
|
||||
testSignVerify(publicKey, privateKey);
|
||||
}
|
||||
|
||||
{
|
||||
// Test async RSA key generation.
|
||||
generateKeyPair('rsa', {
|
||||
publicExponent: 0x10001,
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'der'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
}
|
||||
}, common.mustCall((err, publicKeyDER, privateKey) => {
|
||||
assert.ifError(err);
|
||||
|
||||
// The public key is encoded as DER (which is binary) instead of PEM. We
|
||||
// will still need to convert it to PEM for testing.
|
||||
assert(Buffer.isBuffer(publicKeyDER));
|
||||
const publicKey = convertDERToPEM('RSA PUBLIC KEY', publicKeyDER);
|
||||
assertApproximateSize(publicKey, 720);
|
||||
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert(pkcs1PrivExp.test(privateKey));
|
||||
assertApproximateSize(privateKey, 3272);
|
||||
|
||||
testEncryptDecrypt(publicKey, privateKey);
|
||||
testSignVerify(publicKey, privateKey);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Test async DSA key generation.
|
||||
generateKeyPair('dsa', {
|
||||
modulusLength: 2048,
|
||||
divisorLength: 256,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'der',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'secret'
|
||||
}
|
||||
}, common.mustCall((err, publicKey, privateKeyDER) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert(spkiExp.test(publicKey));
|
||||
// The private key is DER-encoded.
|
||||
assert(Buffer.isBuffer(privateKeyDER));
|
||||
const privateKey = convertDERToPEM('ENCRYPTED PRIVATE KEY', privateKeyDER);
|
||||
|
||||
assertApproximateSize(publicKey, 1194);
|
||||
assertApproximateSize(privateKey, 1054);
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
assert.throws(() => {
|
||||
testSignVerify(publicKey, privateKey);
|
||||
}, /bad decrypt/);
|
||||
|
||||
// Signing should work with the correct password.
|
||||
testSignVerify(publicKey, {
|
||||
key: privateKey,
|
||||
passphrase: 'secret'
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1
|
||||
// private key.
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'prime256v1',
|
||||
paramEncoding: 'named',
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'sec1',
|
||||
format: 'pem'
|
||||
}
|
||||
}, common.mustCall((err, publicKey, privateKey) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert(spkiExp.test(publicKey));
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert(sec1Exp.test(privateKey));
|
||||
|
||||
testSignVerify(publicKey, privateKey);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted
|
||||
// private key.
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'P-256',
|
||||
paramEncoding: 'named',
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'top secret'
|
||||
}
|
||||
}, common.mustCall((err, publicKey, privateKey) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert(spkiExp.test(publicKey));
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert(pkcs8EncExp.test(privateKey));
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
assert.throws(() => {
|
||||
testSignVerify(publicKey, privateKey);
|
||||
}, /bad decrypt/);
|
||||
|
||||
testSignVerify(publicKey, {
|
||||
key: privateKey,
|
||||
passphrase: 'top secret'
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Test invalid key types.
|
||||
for (const type of [undefined, null, 0]) {
|
||||
common.expectsError(() => generateKeyPairSync(type, {}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "type" argument must be of type string. Received type ' +
|
||||
typeof type
|
||||
});
|
||||
}
|
||||
|
||||
common.expectsError(() => generateKeyPairSync('rsa2', {}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
message: "The argument 'type' must be one of " +
|
||||
"'rsa', 'dsa', 'ec'. Received 'rsa2'"
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// Missing / invalid publicKeyEncoding.
|
||||
for (const enc of [undefined, null, 0, 'a', true]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: enc,
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${enc}" is invalid for option "publicKeyEncoding"`
|
||||
});
|
||||
}
|
||||
|
||||
// Missing publicKeyEncoding.type.
|
||||
for (const type of [undefined, null, 0, true, {}]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type,
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${type}" is invalid for option ` +
|
||||
'"publicKeyEncoding.type"'
|
||||
});
|
||||
}
|
||||
|
||||
// Missing / invalid publicKeyEncoding.format.
|
||||
for (const format of [undefined, null, 0, false, 'a', {}]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${format}" is invalid for option ` +
|
||||
'"publicKeyEncoding.format"'
|
||||
});
|
||||
}
|
||||
|
||||
// Missing / invalid privateKeyEncoding.
|
||||
for (const enc of [undefined, null, 0, 'a', true]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: enc
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${enc}" is invalid for option "privateKeyEncoding"`
|
||||
});
|
||||
}
|
||||
|
||||
// Missing / invalid privateKeyEncoding.type.
|
||||
for (const type of [undefined, null, 0, true, {}]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type,
|
||||
format: 'pem'
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${type}" is invalid for option ` +
|
||||
'"privateKeyEncoding.type"'
|
||||
});
|
||||
}
|
||||
|
||||
// Missing / invalid privateKeyEncoding.format.
|
||||
for (const format of [undefined, null, 0, false, 'a', {}]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${format}" is invalid for option ` +
|
||||
'"privateKeyEncoding.format"'
|
||||
});
|
||||
}
|
||||
|
||||
// cipher of invalid type.
|
||||
for (const cipher of [0, true, {}]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem',
|
||||
cipher
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${cipher}" is invalid for option ` +
|
||||
'"privateKeyEncoding.cipher"'
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid cipher.
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'foo',
|
||||
passphrase: 'secret'
|
||||
}
|
||||
}), {
|
||||
type: Error,
|
||||
message: 'Unknown cipher'
|
||||
});
|
||||
|
||||
// cipher, but no valid passphrase.
|
||||
for (const passphrase of [undefined, null, 5, false, true]) {
|
||||
common.expectsError(() => generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase
|
||||
}
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${passphrase}" is invalid for option ` +
|
||||
'"privateKeyEncoding.passphrase"'
|
||||
});
|
||||
}
|
||||
|
||||
// Test invalid callbacks.
|
||||
for (const cb of [undefined, null, 0, {}]) {
|
||||
common.expectsError(() => generateKeyPair('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
||||
}, cb), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_CALLBACK'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Test RSA parameters.
|
||||
{
|
||||
// Test invalid modulus lengths.
|
||||
for (const modulusLength of [undefined, null, 'a', true, {}, [], 512.1, -1]) {
|
||||
common.expectsError(() => generateKeyPair('rsa', {
|
||||
modulusLength
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${modulusLength}" is invalid for option ` +
|
||||
'"modulusLength"'
|
||||
});
|
||||
}
|
||||
|
||||
// Test invalid exponents.
|
||||
for (const publicExponent of ['a', true, {}, [], 3.5, -1]) {
|
||||
common.expectsError(() => generateKeyPair('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicExponent
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${publicExponent}" is invalid for option ` +
|
||||
'"publicExponent"'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Test DSA parameters.
|
||||
{
|
||||
// Test invalid modulus lengths.
|
||||
for (const modulusLength of [undefined, null, 'a', true, {}, [], 4096.1]) {
|
||||
common.expectsError(() => generateKeyPair('dsa', {
|
||||
modulusLength
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${modulusLength}" is invalid for option ` +
|
||||
'"modulusLength"'
|
||||
});
|
||||
}
|
||||
|
||||
// Test invalid divisor lengths.
|
||||
for (const divisorLength of ['a', true, {}, [], 4096.1]) {
|
||||
common.expectsError(() => generateKeyPair('dsa', {
|
||||
modulusLength: 2048,
|
||||
divisorLength
|
||||
}), {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${divisorLength}" is invalid for option ` +
|
||||
'"divisorLength"'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Test EC parameters.
|
||||
{
|
||||
// Test invalid curves.
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync('ec', {
|
||||
namedCurve: 'abcdef',
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
||||
});
|
||||
}, {
|
||||
type: TypeError,
|
||||
message: 'Invalid ECDH curve name'
|
||||
});
|
||||
|
||||
// It should recognize both NIST and standard curve names.
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'P-256',
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
||||
}, common.mustCall((err, publicKey, privateKey) => {
|
||||
assert.ifError(err);
|
||||
}));
|
||||
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'secp192k1',
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
||||
}, common.mustCall((err, publicKey, privateKey) => {
|
||||
assert.ifError(err);
|
||||
}));
|
||||
}
|
||||
|
||||
// Test invalid key encoding types.
|
||||
{
|
||||
// Invalid public key type.
|
||||
for (const type of ['foo', 'pkcs8', 'sec1']) {
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: { type, format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
||||
});
|
||||
}, {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${type}" is invalid for option ` +
|
||||
'"publicKeyEncoding.type"'
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid private key type.
|
||||
for (const type of ['foo', 'spki']) {
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type, format: 'pem' }
|
||||
});
|
||||
}, {
|
||||
type: TypeError,
|
||||
code: 'ERR_INVALID_OPT_VALUE',
|
||||
message: `The value "${type}" is invalid for option ` +
|
||||
'"privateKeyEncoding.type"'
|
||||
});
|
||||
}
|
||||
|
||||
// Key encoding doesn't match key type.
|
||||
for (const type of ['dsa', 'ec']) {
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync(type, {
|
||||
modulusLength: 4096,
|
||||
namedCurve: 'P-256',
|
||||
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
||||
});
|
||||
}, {
|
||||
type: Error,
|
||||
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
||||
message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
|
||||
});
|
||||
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync(type, {
|
||||
modulusLength: 4096,
|
||||
namedCurve: 'P-256',
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
|
||||
});
|
||||
}, {
|
||||
type: Error,
|
||||
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
||||
message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
|
||||
});
|
||||
}
|
||||
|
||||
for (const type of ['rsa', 'dsa']) {
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync(type, {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'sec1', format: 'pem' }
|
||||
});
|
||||
}, {
|
||||
type: Error,
|
||||
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
||||
message: 'The selected key encoding sec1 can only be used for EC keys.'
|
||||
});
|
||||
}
|
||||
|
||||
// Attempting to encrypt a non-PKCS#8 key.
|
||||
for (const type of ['pkcs1', 'sec1']) {
|
||||
common.expectsError(() => {
|
||||
generateKeyPairSync(type === 'pkcs1' ? 'rsa' : 'ec', {
|
||||
modulusLength: 4096,
|
||||
namedCurve: 'P-256',
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: {
|
||||
type,
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'hello'
|
||||
}
|
||||
});
|
||||
}, {
|
||||
type: Error,
|
||||
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
|
||||
message: `The selected key encoding ${type} does not support encryption.`
|
||||
});
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ const { getSystemErrorName } = require('util');
|
||||
delete providers.WORKER;
|
||||
if (!common.isMainThread)
|
||||
delete providers.INSPECTORJSBINDING;
|
||||
delete providers.KEYPAIRGENREQUEST;
|
||||
|
||||
const objKeys = Object.keys(providers);
|
||||
if (objKeys.length > 0)
|
||||
|
Loading…
Reference in New Issue
Block a user