summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/crypto.md278
-rw-r--r--doc/api/errors.md5
-rw-r--r--lib/crypto.js8
-rw-r--r--lib/internal/crypto/cipher.js33
-rw-r--r--lib/internal/crypto/hash.js9
-rw-r--r--lib/internal/crypto/keygen.js135
-rw-r--r--lib/internal/crypto/keys.js337
-rw-r--r--lib/internal/crypto/sig.js31
-rw-r--r--lib/internal/errors.js2
-rw-r--r--node.gyp1
-rw-r--r--src/env.h4
-rw-r--r--src/node_crypto.cc1292
-rw-r--r--src/node_crypto.h168
-rw-r--r--src/util.h23
-rw-r--r--test/parallel/test-crypto-cipheriv-decipheriv.js8
-rw-r--r--test/parallel/test-crypto-hmac.js88
-rw-r--r--test/parallel/test-crypto-key-objects.js107
-rw-r--r--test/parallel/test-crypto-keygen.js117
-rw-r--r--test/parallel/test-crypto-rsa-dsa.js2
-rw-r--r--test/parallel/test-crypto-sign-verify.js2
-rw-r--r--tools/doc/type-parser.js1
21 files changed, 1998 insertions, 653 deletions
diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index 736ac360c9..017eb91e67 100644
--- a/doc/api/crypto.md
+++ b/doc/api/crypto.md
@@ -1101,6 +1101,81 @@ encoding of `'utf8'` is enforced. If `data` is a [`Buffer`][], `TypedArray`, or
This can be called many times with new data as it is streamed.
+## Class: KeyObject
+<!-- YAML
+added: REPLACEME
+-->
+
+Node.js uses an internal `KeyObject` class which should not be accessed
+directly. Instead, factory functions exist to create instances of this class
+in a secure manner, see [`crypto.createSecretKey()`][],
+[`crypto.createPublicKey()`][] and [`crypto.createPrivateKey()`][]. A
+`KeyObject` can represent a symmetric or asymmetric key, and each kind of key
+exposes different functions.
+
+Most applications should consider using the new `KeyObject` API instead of
+passing keys as strings or `Buffer`s due to improved security features.
+
+### keyObject.asymmetricKeyType
+<!-- YAML
+added: REPLACEME
+-->
+* {string}
+
+For asymmetric keys, this property represents the type of the embedded key
+(`'rsa'`, `'dsa'` or `'ec'`). This property is `undefined` for symmetric keys.
+
+### keyObject.export([options])
+<!-- YAML
+added: REPLACEME
+-->
+* `options`: {Object}
+* Returns: {string | Buffer}
+
+For symmetric keys, this function allocates a `Buffer` containing the key
+material and ignores any options.
+
+For asymmetric keys, the `options` parameter is used to determine the export
+format.
+
+For public keys, the following encoding options can be used:
+
+* `type`: {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
+* `format`: {string} Must be `'pem'` or `'der'`.
+
+For private keys, the following encoding options can be used:
+
+* `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 | Buffer} The passphrase to use for encryption, see
+ `cipher`.
+
+When PEM encoding was selected, the result will be a string, otherwise it will
+be a buffer containing the data encoded as DER.
+
+### keyObject.symmetricSize
+<!-- YAML
+added: REPLACEME
+-->
+* {number}
+
+For secret keys, this property represents the size of the key in bytes. This
+property is `undefined` for asymmetric keys.
+
+### keyObject.type
+<!-- YAML
+added: REPLACEME
+-->
+* {string}
+
+Depending on the type of this `KeyObject`, this property is either
+`'secret'` for secret (symmetric) keys, `'public'` for public (asymmetric) keys
+or `'private'` for private (asymmetric) keys.
+
## Class: Sign
<!-- YAML
added: v0.1.92
@@ -1169,13 +1244,14 @@ console.log(sign.sign(privateKey, 'hex'));
<!-- YAML
added: v0.1.92
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: This function now supports key objects.
- version: v8.0.0
pr-url: https://github.com/nodejs/node/pull/11705
description: Support for RSASSA-PSS and additional options was added.
-->
-* `privateKey` {string | Object}
- - `key` {string}
- - `passphrase` {string}
+* `privateKey` {Object | string | Buffer | KeyObject}
- `padding` {integer}
- `saltLength` {integer}
* `outputEncoding` {string} The [encoding][] of the return value.
@@ -1184,12 +1260,10 @@ changes:
Calculates the signature on all the data passed through using either
[`sign.update()`][] or [`sign.write()`][stream-writable-write].
-The `privateKey` argument can be an object or a string. If `privateKey` is a
-string, it is treated as a raw key with no passphrase. If `privateKey` is an
-object, it must contain one or more of the following properties:
+If `privateKey` is not a [`KeyObject`][], this function behaves as if
+`privateKey` had been passed to [`crypto.createPrivateKey()`][]. If it is an
+object, the following additional properties can be passed:
-* `key`: {string} - PEM encoded private key (required)
-* `passphrase`: {string} - passphrase for the private key
* `padding`: {integer} - Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
@@ -1299,18 +1373,20 @@ changes:
pr-url: https://github.com/nodejs/node/pull/11705
description: Support for RSASSA-PSS and additional options was added.
-->
-* `object` {string | Object}
+* `object` {Object | string | Buffer | KeyObject}
+ - `padding` {integer}
+ - `saltLength` {integer}
* `signature` {string | Buffer | TypedArray | DataView}
* `signatureEncoding` {string} The [encoding][] of the `signature` string.
* Returns: {boolean} `true` or `false` depending on the validity of the
signature for the data and public key.
Verifies the provided data using the given `object` and `signature`.
-The `object` argument can be either a string containing a PEM encoded object,
-which can be an RSA public key, a DSA public key, or an X.509 certificate,
-or an object with one or more of the following properties:
-* `key`: {string} - PEM encoded public key (required)
+If `object` is not a [`KeyObject`][], this function behaves as if
+`object` had been passed to [`crypto.createPublicKey()`][]. If it is an
+object, the following additional properties can be passed:
+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
@@ -1436,6 +1512,9 @@ Adversaries][] for details.
<!-- YAML
added: v0.1.94
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: The `key` argument can now be a `KeyObject`.
- version: v11.2.0
pr-url: https://github.com/nodejs/node/pull/24081
description: The cipher `chacha20-poly1305` is now supported.
@@ -1452,7 +1531,7 @@ changes:
need an initialization vector.
-->
* `algorithm` {string}
-* `key` {string | Buffer | TypedArray | DataView}
+* `key` {string | Buffer | TypedArray | DataView | KeyObject}
* `iv` {string | Buffer | TypedArray | DataView}
* `options` {Object} [`stream.transform` options][]
* Returns: {Cipher}
@@ -1474,7 +1553,8 @@ display the available cipher algorithms.
The `key` is the raw key used by the `algorithm` and `iv` is an
[initialization vector][]. Both arguments must be `'utf8'` encoded strings,
-[Buffers][`Buffer`], `TypedArray`, or `DataView`s. If the cipher does not need
+[Buffers][`Buffer`], `TypedArray`, or `DataView`s. The `key` may optionally be
+a [`KeyObject`][] of type `secret`. If the cipher does not need
an initialization vector, `iv` may be `null`.
Initialization vectors should be unpredictable and unique; ideally, they will be
@@ -1525,6 +1605,9 @@ to create the `Decipher` object.
<!-- YAML
added: v0.1.94
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: The `key` argument can now be a `KeyObject`.
- version: v11.2.0
pr-url: https://github.com/nodejs/node/pull/24081
description: The cipher `chacha20-poly1305` is now supported.
@@ -1563,7 +1646,8 @@ display the available cipher algorithms.
The `key` is the raw key used by the `algorithm` and `iv` is an
[initialization vector][]. Both arguments must be `'utf8'` encoded strings,
-[Buffers][`Buffer`], `TypedArray`, or `DataView`s. If the cipher does not need
+[Buffers][`Buffer`], `TypedArray`, or `DataView`s. The `key` may optionally be
+a [`KeyObject`][] of type `secret`. If the cipher does not need
an initialization vector, `iv` may be `null`.
Initialization vectors should be unpredictable and unique; ideally, they will be
@@ -1674,9 +1758,13 @@ input.on('readable', () => {
### crypto.createHmac(algorithm, key[, options])
<!-- YAML
added: v0.1.94
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: The `key` argument can now be a `KeyObject`.
-->
* `algorithm` {string}
-* `key` {string | Buffer | TypedArray | DataView}
+* `key` {string | Buffer | TypedArray | DataView | KeyObject}
* `options` {Object} [`stream.transform` options][]
* Returns: {Hmac}
@@ -1689,7 +1777,8 @@ On recent releases of OpenSSL, `openssl list -digest-algorithms`
(`openssl list-message-digest-algorithms` for older versions of OpenSSL) will
display the available digest algorithms.
-The `key` is the HMAC key used to generate the cryptographic HMAC hash.
+The `key` is the HMAC key used to generate the cryptographic HMAC hash. If it is
+a [`KeyObject`][], its type must be `secret`.
Example: generating the sha256 HMAC of a file
@@ -1711,6 +1800,47 @@ input.on('readable', () => {
});
```
+### crypto.createPrivateKey(key)
+<!-- YAML
+added: REPLACEME
+-->
+* `key` {Object | string | Buffer}
+ - `key`: {string | Buffer} The key material, either in PEM or DER format.
+ - `format`: {string} Must be `'pem'` or `'der'`. **Default:** `'pem'`.
+ - `type`: {string} Must be `'pkcs1'`, `'pkcs8'` or `'sec1'`. This option is
+ required only if the `format` is `'der'` and ignored if it is `'pem'`.
+ - `passphrase`: {string | Buffer} The passphrase to use for decryption.
+* Returns: {KeyObject}
+
+Creates and returns a new key object containing a private key. If `key` is a
+string or `Buffer`, `format` is assumed to be `'pem'`; otherwise, `key`
+must be an object with the properties described above.
+
+### crypto.createPublicKey(key)
+<!-- YAML
+added: REPLACEME
+-->
+* `key` {Object | string | Buffer}
+ - `key`: {string | Buffer}
+ - `format`: {string} Must be `'pem'` or `'der'`. **Default:** `'pem'`.
+ - `type`: {string} Must be `'pkcs1'` or `'spki'`. This option is required
+ only if the `format` is `'der'`.
+* Returns: {KeyObject}
+
+Creates and returns a new key object containing a public key. If `key` is a
+string or `Buffer`, `format` is assumed to be `'pem'`; otherwise, `key`
+must be an object with the properties described above.
+
+### crypto.createSecretKey(key)
+<!-- YAML
+added: REPLACEME
+-->
+* `key` {Buffer}
+* Returns: {KeyObject}
+
+Creates and returns a new key object containing a secret key for symmetric
+encryption or `Hmac`.
+
### crypto.createSign(algorithm[, options])
<!-- YAML
added: v0.1.92
@@ -1740,6 +1870,11 @@ signing algorithms. Optional `options` argument controls the
### crypto.generateKeyPair(type, options, callback)
<!-- YAML
added: v10.12.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: The `generateKeyPair` and `generateKeyPairSync` functions now
+ produce key objects if no encoding was specified.
-->
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
* `options`: {Object}
@@ -1747,27 +1882,22 @@ added: v10.12.0
- `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`.
+ - `publicKeyEncoding`: {Object} See [`keyObject.export()`][].
+ - `privateKeyEncoding`: {Object} See [`keyObject.export()`][].
* `callback`: {Function}
- `err`: {Error}
- - `publicKey`: {string|Buffer}
- - `privateKey`: {string|Buffer}
+ - `publicKey`: {string | Buffer | KeyObject}
+ - `privateKey`: {string | Buffer | KeyObject}
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
are currently supported.
+If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
+behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
+the respective part of the key is returned as a [`KeyObject`].
+
It is recommended to encode public keys as `'spki'` and private keys as
-`'pkcs8'` with encryption:
+`'pkcs8'` with encryption for long-term storage:
```js
const { generateKeyPair } = require('crypto');
@@ -1789,11 +1919,7 @@ generateKeyPair('rsa', {
```
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.
+`publicKey` / `privateKey` representing the generated key pair.
If this method is invoked as its [`util.promisify()`][]ed version, it returns
a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
@@ -1801,6 +1927,11 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
### crypto.generateKeyPairSync(type, options)
<!-- YAML
added: v10.12.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: The `generateKeyPair` and `generateKeyPairSync` functions now
+ produce key objects if no encoding was specified.
-->
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
* `options`: {Object}
@@ -1818,10 +1949,11 @@ added: v10.12.0
- `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`.
+ - `passphrase`: {string | Buffer} The passphrase to use for encryption, see
+ `cipher`.
* Returns: {Object}
- - `publicKey`: {string|Buffer}
- - `privateKey`: {string|Buffer}
+ - `publicKey`: {string | Buffer | KeyObject}
+ - `privateKey`: {string | Buffer | KeyObject}
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
are currently supported.
@@ -2062,10 +2194,12 @@ An array of supported digest functions can be retrieved using
### crypto.privateDecrypt(privateKey, buffer)
<!-- YAML
added: v0.11.14
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: This function now supports key objects.
-->
-* `privateKey` {Object | string}
- - `key` {string} A PEM encoded private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `privateKey` {Object | string | Buffer | KeyObject}
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING`,
`crypto.constants.RSA_PKCS1_PADDING`, or
@@ -2076,16 +2210,22 @@ added: v0.11.14
Decrypts `buffer` with `privateKey`. `buffer` was previously encrypted using
the corresponding public key, for example using [`crypto.publicEncrypt()`][].
-`privateKey` can be an object or a string. If `privateKey` is a string, it is
-treated as the key with no passphrase and will use `RSA_PKCS1_OAEP_PADDING`.
+If `privateKey` is not a [`KeyObject`][], this function behaves as if
+`privateKey` had been passed to [`crypto.createPrivateKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_OAEP_PADDING`.
### crypto.privateEncrypt(privateKey, buffer)
<!-- YAML
added: v1.1.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: This function now supports key objects.
-->
-* `privateKey` {Object | string}
- - `key` {string} A PEM encoded private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `privateKey` {Object | string | Buffer | KeyObject}
+ - `key` {string | Buffer | KeyObject} A PEM encoded private key.
+ - `passphrase` {string | Buffer} An optional passphrase for the private key.
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING` or
`crypto.constants.RSA_PKCS1_PADDING`.
@@ -2095,16 +2235,21 @@ added: v1.1.0
Encrypts `buffer` with `privateKey`. The returned data can be decrypted using
the corresponding public key, for example using [`crypto.publicDecrypt()`][].
-`privateKey` can be an object or a string. If `privateKey` is a string, it is
-treated as the key with no passphrase and will use `RSA_PKCS1_PADDING`.
+If `privateKey` is not a [`KeyObject`][], this function behaves as if
+`privateKey` had been passed to [`crypto.createPrivateKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_PADDING`.
### crypto.publicDecrypt(key, buffer)
<!-- YAML
added: v1.1.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: This function now supports key objects.
-->
-* `key` {Object | string}
- - `key` {string} A PEM encoded public or private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `key` {Object | string | Buffer | KeyObject}
+ - `passphrase` {string | Buffer} An optional passphrase for the private key.
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING` or
`crypto.constants.RSA_PKCS1_PADDING`.
@@ -2114,8 +2259,10 @@ added: v1.1.0
Decrypts `buffer` with `key`.`buffer` was previously encrypted using
the corresponding private key, for example using [`crypto.privateEncrypt()`][].
-`key` can be an object or a string. If `key` is a string, it is treated as
-the key with no passphrase and will use `RSA_PKCS1_PADDING`.
+If `key` is not a [`KeyObject`][], this function behaves as if
+`key` had been passed to [`crypto.createPublicKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_PADDING`.
Because RSA public keys can be derived from private keys, a private key may
be passed instead of a public key.
@@ -2123,10 +2270,14 @@ be passed instead of a public key.
### crypto.publicEncrypt(key, buffer)
<!-- YAML
added: v0.11.14
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/24234
+ description: This function now supports key objects.
-->
-* `key` {Object | string}
- - `key` {string} A PEM encoded public or private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `key` {Object | string | Buffer | KeyObject}
+ - `key` {string | Buffer | KeyObject} A PEM encoded public or private key.
+ - `passphrase` {string | Buffer} An optional passphrase for the private key.
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING`,
`crypto.constants.RSA_PKCS1_PADDING`, or
@@ -2138,8 +2289,10 @@ Encrypts the content of `buffer` with `key` and returns a new
[`Buffer`][] with encrypted content. The returned data can be decrypted using
the corresponding private key, for example using [`crypto.privateDecrypt()`][].
-`key` can be an object or a string. If `key` is a string, it is treated as
-the key with no passphrase and will use `RSA_PKCS1_OAEP_PADDING`.
+If `key` is not a [`KeyObject`][], this function behaves as if
+`key` had been passed to [`crypto.createPublicKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_OAEP_PADDING`.
Because RSA public keys can be derived from private keys, a private key may
be passed instead of a public key.
@@ -2917,6 +3070,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`Buffer`]: buffer.html
[`EVP_BytesToKey`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html
+[`KeyObject`]: #crypto_class_keyobject
[`UV_THREADPOOL_SIZE`]: cli.html#cli_uv_threadpool_size_size
[`cipher.final()`]: #crypto_cipher_final_outputencoding
[`cipher.update()`]: #crypto_cipher_update_data_inputencoding_outputencoding
@@ -2928,6 +3082,9 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`crypto.createECDH()`]: #crypto_crypto_createecdh_curvename
[`crypto.createHash()`]: #crypto_crypto_createhash_algorithm_options
[`crypto.createHmac()`]: #crypto_crypto_createhmac_algorithm_key_options
+[`crypto.createPrivateKey()`]: #crypto_crypto_createprivatekey_key
+[`crypto.createPublicKey()`]: #crypto_crypto_createpublickey_key
+[`crypto.createSecretKey()`]: #crypto_crypto_createsecretkey_key
[`crypto.createSign()`]: #crypto_crypto_createsign_algorithm_options
[`crypto.createVerify()`]: #crypto_crypto_createverify_algorithm_options
[`crypto.getCurves()`]: #crypto_crypto_getcurves
@@ -2949,6 +3106,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`hash.update()`]: #crypto_hash_update_data_inputencoding
[`hmac.digest()`]: #crypto_hmac_digest_encoding
[`hmac.update()`]: #crypto_hmac_update_data_inputencoding
+[`keyObject.export()`]: #crypto_keyobject_export_options
[`sign.sign()`]: #crypto_sign_sign_privatekey_outputencoding
[`sign.update()`]: #crypto_sign_update_data_inputencoding
[`stream.Writable` options]: stream.html#stream_constructor_new_stream_writable_options
diff --git a/doc/api/errors.md b/doc/api/errors.md
index a2ae4d32be..b2a24e3b53 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -763,6 +763,11 @@ The selected public or private key encoding is incompatible with other options.
An invalid [crypto digest algorithm][] was specified.
+<a id="ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"></a>
+### ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE
+
+The given crypto key object's type is invalid for the attempted operation.
+
<a id="ERR_CRYPTO_INVALID_STATE"></a>
### ERR_CRYPTO_INVALID_STATE
diff --git a/lib/crypto.js b/lib/crypto.js
index 4707ab2b35..a0062a3d53 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -60,6 +60,11 @@ const {
generateKeyPairSync
} = require('internal/crypto/keygen');
const {
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey
+} = require('internal/crypto/keys');
+const {
DiffieHellman,
DiffieHellmanGroup,
ECDH
@@ -149,6 +154,9 @@ module.exports = exports = {
createECDH,
createHash,
createHmac,
+ createPrivateKey,
+ createPublicKey,
+ createSecretKey,
createSign,
createVerify,
getCiphers,
diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js
index 1e5dc91c8d..0e8e5c4cf8 100644
--- a/lib/internal/crypto/cipher.js
+++ b/lib/internal/crypto/cipher.js
@@ -13,6 +13,11 @@ const {
const { validateString } = require('internal/validators');
const {
+ preparePrivateKey,
+ preparePublicOrPrivateKey,
+ prepareSecretKey
+} = require('internal/crypto/keys');
+const {
getDefaultEncoding,
kHandle,
legacyNativeHandle,
@@ -37,19 +42,25 @@ const { deprecate, normalizeEncoding } = require('internal/util');
// Lazy loaded for startup performance.
let StringDecoder;
-function rsaFunctionFor(method, defaultPadding) {
+function rsaFunctionFor(method, defaultPadding, keyType) {
return (options, buffer) => {
- const key = options.key || options;
+ const { format, type, data, passphrase } =
+ keyType === 'private' ?
+ preparePrivateKey(options) :
+ preparePublicOrPrivateKey(options);
const padding = options.padding || defaultPadding;
- const passphrase = options.passphrase || null;
- return method(toBuf(key), buffer, padding, passphrase);
+ return method(data, format, type, passphrase, buffer, padding);
};
}
-const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING);
-const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING);
-const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING);
-const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING);
+const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
+ 'public');
+const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
+ 'private');
+const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
+ 'private');
+const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
+ 'public');
function getDecoder(decoder, encoding) {
encoding = normalizeEncoding(encoding);
@@ -104,11 +115,7 @@ function createCipher(cipher, password, options, decipher) {
function createCipherWithIV(cipher, key, options, decipher, iv) {
validateString(cipher, 'cipher');
- key = toBuf(key);
- if (!isArrayBufferView(key)) {
- throw invalidArrayBufferView('key', key);
- }
-
+ key = prepareSecretKey(key);
iv = toBuf(iv);
if (iv !== null && !isArrayBufferView(iv)) {
throw invalidArrayBufferView('iv', iv);
diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js
index f289d11cf8..713ded3d18 100644
--- a/lib/internal/crypto/hash.js
+++ b/lib/internal/crypto/hash.js
@@ -12,6 +12,10 @@ const {
toBuf
} = require('internal/crypto/util');
+const {
+ prepareSecretKey
+} = require('internal/crypto/keys');
+
const { Buffer } = require('buffer');
const {
@@ -88,10 +92,7 @@ function Hmac(hmac, key, options) {
if (!(this instanceof Hmac))
return new Hmac(hmac, key, options);
validateString(hmac, 'hmac');
- if (typeof key !== 'string' && !isArrayBufferView(key)) {
- throw new ERR_INVALID_ARG_TYPE('key',
- ['string', 'TypedArray', 'DataView'], key);
- }
+ key = prepareSecretKey(key);
this[kHandle] = new _Hmac();
this[kHandle].init(hmac, toBuf(key));
this[kState] = {
diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js
index 7222d301f0..7c0c411043 100644
--- a/lib/internal/crypto/keygen.js
+++ b/lib/internal/crypto/keygen.js
@@ -6,24 +6,32 @@ const {
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
+ 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_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
ERR_INVALID_OPT_VALUE
} = 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;
@@ -38,6 +46,9 @@ function generateKeyPair(type, options, 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);
};
@@ -69,86 +80,32 @@ function handleError(impl, wrap) {
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;
+ 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.type', strPublicType);
+ throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding', publicKeyEncoding);
}
- let publicFormat;
- if (strPublicFormat === 'der') {
- publicFormat = PK_FORMAT_DER;
- } else if (strPublicFormat === 'pem') {
- publicFormat = PK_FORMAT_PEM;
+ 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('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 (privateFormat === PK_FORMAT_DER &&
- (privateType === PK_ENCODING_PKCS1 ||
- privateType === PK_ENCODING_SEC1)) {
- 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 {
@@ -181,8 +138,8 @@ function check(type, options, callback) {
}
impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
- publicType, publicFormat,
- privateType, privateFormat,
+ publicFormat, publicType,
+ privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
@@ -200,8 +157,8 @@ function check(type, options, callback) {
}
impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
- publicType, publicFormat,
- privateType, privateFormat,
+ publicFormat, publicType,
+ privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
@@ -219,8 +176,8 @@ function check(type, options, callback) {
throw new ERR_INVALID_OPT_VALUE('paramEncoding', paramEncoding);
impl = (wrap) => generateKeyPairEC(namedCurve, paramEncoding,
- publicType, publicFormat,
- privateType, privateFormat,
+ publicFormat, publicType,
+ privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js
new file mode 100644
index 0000000000..ad82835080
--- /dev/null
+++ b/lib/internal/crypto/keys.js
@@ -0,0 +1,337 @@
+'use strict';
+
+const {
+ KeyObject: KeyObjectHandle,
+ kKeyTypeSecret,
+ kKeyTypePublic,
+ kKeyTypePrivate,
+ kKeyFormatPEM,
+ kKeyFormatDER,
+ kKeyEncodingPKCS1,
+ kKeyEncodingPKCS8,
+ kKeyEncodingSPKI,
+ kKeyEncodingSEC1
+} = internalBinding('crypto');
+const {
+ ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
+ ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
+ ERR_INVALID_OPT_VALUE,
+ ERR_OUT_OF_RANGE
+} = require('internal/errors').codes;
+const { kHandle } = require('internal/crypto/util');
+
+const { isArrayBufferView } = require('internal/util/types');
+
+const kKeyType = Symbol('kKeyType');
+
+const encodingNames = [];
+for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
+ [kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
+ encodingNames[m[0]] = m[1];
+
+class KeyObject {
+ constructor(type, handle) {
+ if (type !== 'secret' && type !== 'public' && type !== 'private')
+ throw new ERR_INVALID_ARG_VALUE('type', type);
+ if (typeof handle !== 'object')
+ throw new ERR_INVALID_ARG_TYPE('handle', 'string', handle);
+
+ this[kKeyType] = type;
+
+ Object.defineProperty(this, kHandle, {
+ value: handle,
+ enumerable: false,
+ configurable: false,
+ writable: false
+ });
+ }
+
+ get type() {
+ return this[kKeyType];
+ }
+}
+
+class SecretKeyObject extends KeyObject {
+ constructor(handle) {
+ super('secret', handle);
+ }
+
+ get symmetricKeySize() {
+ return this[kHandle].getSymmetricKeySize();
+ }
+
+ export() {
+ return this[kHandle].export();
+ }
+}
+
+const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
+
+class AsymmetricKeyObject extends KeyObject {
+ get asymmetricKeyType() {
+ return this[kAsymmetricKeyType] ||
+ (this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType());
+ }
+}
+
+class PublicKeyObject extends AsymmetricKeyObject {
+ constructor(handle) {
+ super('public', handle);
+ }
+
+ export(encoding) {
+ const {
+ format,
+ type
+ } = parsePublicKeyEncoding(encoding, this.asymmetricKeyType);
+ return this[kHandle].export(format, type);
+ }
+}
+
+class PrivateKeyObject extends AsymmetricKeyObject {
+ constructor(handle) {
+ super('private', handle);
+ }
+
+ export(encoding) {
+ const {
+ format,
+ type,
+ cipher,
+ passphrase
+ } = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType);
+ return this[kHandle].export(format, type, cipher, passphrase);
+ }
+}
+
+function parseKeyFormat(formatStr, defaultFormat, optionName) {
+ if (formatStr === undefined && defaultFormat !== undefined)
+ return defaultFormat;
+ else if (formatStr === 'pem')
+ return kKeyFormatPEM;
+ else if (formatStr === 'der')
+ return kKeyFormatDER;
+ throw new ERR_INVALID_OPT_VALUE(optionName, formatStr);
+}
+
+function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
+ if (typeStr === undefined && !required) {
+ return undefined;
+ } else if (typeStr === 'pkcs1') {
+ if (keyType !== undefined && keyType !== 'rsa') {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ typeStr, 'can only be used for RSA keys');
+ }
+ return kKeyEncodingPKCS1;
+ } else if (typeStr === 'spki' && isPublic !== false) {
+ return kKeyEncodingSPKI;
+ } else if (typeStr === 'pkcs8' && isPublic !== true) {
+ return kKeyEncodingPKCS8;
+ } else if (typeStr === 'sec1' && isPublic !== true) {
+ if (keyType !== undefined && keyType !== 'ec') {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ typeStr, 'can only be used for EC keys');
+ }
+ return kKeyEncodingSEC1;
+ }
+
+ throw new ERR_INVALID_OPT_VALUE(optionName, typeStr);
+}
+
+function option(name, objName) {
+ return objName === undefined ? name : `${objName}.${name}`;
+}
+
+function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
+ const { format: formatStr, type: typeStr } = enc;
+
+ const isInput = keyType === undefined;
+ const format = parseKeyFormat(formatStr,
+ isInput ? kKeyFormatPEM : undefined,
+ option('format', objName));
+
+ const type = parseKeyType(typeStr,
+ !isInput || format === kKeyFormatDER,
+ keyType,
+ isPublic,
+ option('type', objName));
+
+ return { format, type };
+}
+
+function isStringOrBuffer(val) {
+ return typeof val === 'string' || isArrayBufferView(val);
+}
+
+function parseKeyEncoding(enc, keyType, isPublic, objName) {
+ const isInput = keyType === undefined;
+
+ const {
+ format,
+ type
+ } = parseKeyFormatAndType(enc, keyType, isPublic, objName);
+
+ let cipher, passphrase;
+ if (isPublic !== true) {
+ ({ cipher, passphrase } = enc);
+
+ if (!isInput && cipher != null) {
+ if (typeof cipher !== 'string')
+ throw new ERR_INVALID_OPT_VALUE(option('cipher', objName), cipher);
+ if (format === kKeyFormatDER &&
+ (type === kKeyEncodingPKCS1 ||
+ type === kKeyEncodingSEC1)) {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ encodingNames[type], 'does not support encryption');
+ }
+ }
+
+ if ((isInput && passphrase !== undefined &&
+ !isStringOrBuffer(passphrase)) ||
+ (!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
+ throw new ERR_INVALID_OPT_VALUE(option('passphrase', objName),
+ passphrase);
+ }
+ }
+
+ return { format, type, cipher, passphrase };
+}
+
+// Parses the public key encoding based on an object. keyType must be undefined
+// when this is used to parse an input encoding and must be a valid key type if
+// used to parse an output encoding.
+function parsePublicKeyEncoding(enc, keyType, objName) {
+ return parseKeyFormatAndType(enc, keyType, true, objName);
+}
+
+// Parses the private key encoding based on an object. keyType must be undefined
+// when this is used to parse an input encoding and must be a valid key type if
+// used to parse an output encoding.
+function parsePrivateKeyEncoding(enc, keyType, objName) {
+ return parseKeyEncoding(enc, keyType, false, objName);
+}
+
+function getKeyObjectHandle(key, isPublic, allowKeyObject) {
+ if (!allowKeyObject) {
+ return new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['string', 'Buffer', 'TypedArray', 'DataView'],
+ key
+ );
+ }
+ if (isPublic != null) {
+ const expectedType = isPublic ? 'public' : 'private';
+ if (key.type !== expectedType)
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, expectedType);
+ }
+ return key[kHandle];
+}
+
+function prepareAsymmetricKey(key, isPublic, allowKeyObject = true) {
+ if (isKeyObject(key)) {
+ // Best case: A key object, as simple as that.
+ return { data: getKeyObjectHandle(key, isPublic, allowKeyObject) };
+ } else if (typeof key === 'string' || isArrayBufferView(key)) {
+ // Expect PEM by default, mostly for backward compatibility.
+ return { format: kKeyFormatPEM, data: key };
+ } else if (typeof key === 'object') {
+ const data = key.key;
+ // The 'key' property can be a KeyObject as well to allow specifying
+ // additional options such as padding along with the key.
+ if (isKeyObject(data))
+ return { data: getKeyObjectHandle(data, isPublic, allowKeyObject) };
+ // Either PEM or DER using PKCS#1 or SPKI.
+ if (!isStringOrBuffer(data)) {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['string', 'Buffer', 'TypedArray', 'DataView',
+ ...(allowKeyObject ? ['KeyObject'] : [])],
+ key);
+ }
+ return { data, ...parseKeyEncoding(key, undefined, isPublic) };
+ } else {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['string', 'Buffer', 'TypedArray', 'DataView',
+ ...(allowKeyObject ? ['KeyObject'] : [])],
+ key
+ );
+ }
+}
+
+function preparePublicKey(key, allowKeyObject) {
+ return prepareAsymmetricKey(key, true, allowKeyObject);
+}
+
+function preparePrivateKey(key, allowKeyObject) {
+ return prepareAsymmetricKey(key, false, allowKeyObject);
+}
+
+function preparePublicOrPrivateKey(key, allowKeyObject) {
+ return prepareAsymmetricKey(key, undefined, allowKeyObject);
+}
+
+function prepareSecretKey(key, bufferOnly = false) {
+ if (!isArrayBufferView(key) && (bufferOnly || typeof key !== 'string')) {
+ if (isKeyObject(key) && !bufferOnly) {
+ if (key.type !== 'secret')
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
+ return key[kHandle];
+ } else {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['Buffer', 'TypedArray', 'DataView',
+ ...(bufferOnly ? [] : ['string', 'KeyObject'])],
+ key);
+ }
+ }
+ return key;
+}
+
+function createSecretKey(key) {
+ key = prepareSecretKey(key, true);
+ if (key.byteLength === 0)
+ throw new ERR_OUT_OF_RANGE('key.byteLength', '> 0', key.byteLength);
+ const handle = new KeyObjectHandle(kKeyTypeSecret);
+ handle.init(key);
+ return new SecretKeyObject(handle);
+}
+
+function createPublicKey(key) {
+ const { format, type, data } = preparePublicKey(key, false);
+ const handle = new KeyObjectHandle(kKeyTypePublic);
+ handle.init(data, format, type);
+ return new PublicKeyObject(handle);
+}
+
+function createPrivateKey(key) {
+ const { format, type, data, passphrase } = preparePrivateKey(key, false);
+ const handle = new KeyObjectHandle(kKeyTypePrivate);
+ handle.init(data, format, type, passphrase);
+ return new PrivateKeyObject(handle);
+}
+
+function isKeyObject(key) {
+ return key instanceof KeyObject;
+}
+
+module.exports = {
+ // Public API.
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey,
+
+ // These are designed for internal use only and should not be exposed.
+ parsePublicKeyEncoding,
+ parsePrivateKeyEncoding,
+ preparePublicKey,
+ preparePrivateKey,
+ preparePublicOrPrivateKey,
+ prepareSecretKey,
+ SecretKeyObject,
+ PublicKeyObject,
+ PrivateKeyObject,
+ isKeyObject
+};
diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js
index fa2d4998b6..32f7c37ec2 100644
--- a/lib/internal/crypto/sig.js
+++ b/lib/internal/crypto/sig.js
@@ -17,6 +17,10 @@ const {
toBuf,
validateArrayBufferView,
} = require('internal/crypto/util');
+const {
+ preparePrivateKey,
+ preparePublicKey
+} = require('internal/crypto/keys');
const { Writable } = require('stream');
function Sign(algorithm, options) {
@@ -71,21 +75,18 @@ Sign.prototype.sign = function sign(options, encoding) {
if (!options)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
- var key = options.key || options;
- var passphrase = options.passphrase || null;
+ const { data, format, type, passphrase } = preparePrivateKey(options, true);
// Options specific to RSA
- var rsaPadding = getPadding(options);
-
- var pssSaltLength = getSaltLength(options);
+ const rsaPadding = getPadding(options);
+ const pssSaltLength = getSaltLength(options);
- key = validateArrayBufferView(key, 'key');
-
- var ret = this[kHandle].sign(key, passphrase, rsaPadding, pssSaltLength);
+ const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
+ pssSaltLength);
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
- ret = ret.toString(encoding);
+ return ret.toString(encoding);
return ret;
};
@@ -108,7 +109,12 @@ Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update;
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
- var key = options.key || options;
+ const {
+ data,
+ format,
+ type
+ } = preparePublicKey(options, true);
+
sigEncoding = sigEncoding || getDefaultEncoding();
// Options specific to RSA
@@ -116,12 +122,11 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
var pssSaltLength = getSaltLength(options);
- key = validateArrayBufferView(key, 'key');
-
signature = validateArrayBufferView(toBuf(signature, sigEncoding),
'signature');
- return this[kHandle].verify(key, signature, rsaPadding, pssSaltLength);
+ return this[kHandle].verify(data, format, type, signature,
+ rsaPadding, pssSaltLength);
};
legacyNativeHandle(Verify);
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 174281f8a2..8be692ef57 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -573,6 +573,8 @@ 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_KEY_OBJECT_TYPE',
+ 'Invalid key object type %s, expected %s.', TypeError);
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error);
E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error);
diff --git a/node.gyp b/node.gyp
index 8560c1d512..8c42ef446e 100644
--- a/node.gyp
+++ b/node.gyp
@@ -102,6 +102,7 @@
'lib/internal/crypto/diffiehellman.js',
'lib/internal/crypto/hash.js',
'lib/internal/crypto/keygen.js',
+ 'lib/internal/crypto/keys.js',
'lib/internal/crypto/pbkdf2.js',
'lib/internal/crypto/random.js',
'lib/internal/crypto/scrypt.js',
diff --git a/src/env.h b/src/env.h
index c999b258c5..3b76259773 100644
--- a/src/env.h
+++ b/src/env.h
@@ -142,6 +142,9 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(code_string, "code") \
V(config_string, "config") \
V(constants_string, "constants") \
+ V(crypto_dsa_string, "dsa") \
+ V(crypto_ec_string, "ec") \
+ V(crypto_rsa_string, "rsa") \
V(cwd_string, "cwd") \
V(dest_string, "dest") \
V(destroyed_string, "destroyed") \
@@ -324,6 +327,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \
+ V(crypto_key_object_constructor, v8::Function) \
V(domain_callback, v8::Function) \
V(domexception_function, v8::Function) \
V(fd_constructor_template, v8::ObjectTemplate) \
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 011506a34e..b6b8063ccb 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -73,6 +73,7 @@ using v8::DontDelete;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::External;
+using v8::Function;
using v8::FunctionCallback;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
@@ -2695,6 +2696,837 @@ static bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) {
return IsSupportedAuthenticatedMode(cipher);
}
+template <typename T>
+static T* MallocOpenSSL(size_t count) {
+ void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T)));
+ CHECK_NOT_NULL(mem);
+ return static_cast<T*>(mem);
+}
+
+enum class ParsePublicKeyResult {
+ kParsePublicOk,
+ kParsePublicNotRecognized,
+ kParsePublicFailed
+};
+
+static ParsePublicKeyResult TryParsePublicKey(
+ EVPKeyPointer* pkey,
+ const BIOPointer& bp,
+ const char* name,
+ // NOLINTNEXTLINE(runtime/int)
+ std::function<EVP_PKEY*(const unsigned char** p, long l)> parse) {
+ unsigned char* der_data;
+ long der_len; // NOLINT(runtime/int)
+
+ // This skips surrounding data and decodes PEM to DER.
+ {
+ MarkPopErrorOnReturn mark_pop_error_on_return;
+ if (PEM_bytes_read_bio(&der_data, &der_len, nullptr, name,
+ bp.get(), nullptr, nullptr) != 1)
+ return ParsePublicKeyResult::kParsePublicNotRecognized;
+ }
+
+ // OpenSSL might modify the pointer, so we need to make a copy before parsing.
+ const unsigned char* p = der_data;
+ pkey->reset(parse(&p, der_len));
+ OPENSSL_clear_free(der_data, der_len);
+
+ return *pkey ? ParsePublicKeyResult::kParsePublicOk :
+ ParsePublicKeyResult::kParsePublicFailed;
+}
+
+static ParsePublicKeyResult ParsePublicKeyPEM(EVPKeyPointer* pkey,
+ const char* key_pem,
+ int key_pem_len,
+ bool allow_certificate) {
+ BIOPointer bp(BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len));
+ if (!bp)
+ return ParsePublicKeyResult::kParsePublicFailed;
+
+ ParsePublicKeyResult ret;
+
+ // Try PKCS#8 first.
+ ret = TryParsePublicKey(pkey, bp, "PUBLIC KEY",
+ [](const unsigned char** p, long l) { // NOLINT(runtime/int)
+ return d2i_PUBKEY(nullptr, p, l);
+ });
+ if (ret != ParsePublicKeyResult::kParsePublicNotRecognized)
+ return ret;
+
+ // Maybe it is PKCS#1.
+ CHECK(BIO_reset(bp.get()));
+ ret = TryParsePublicKey(pkey, bp, "RSA PUBLIC KEY",
+ [](const unsigned char** p, long l) { // NOLINT(runtime/int)
+ return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l);
+ });
+ if (ret != ParsePublicKeyResult::kParsePublicNotRecognized ||
+ !allow_certificate)
+ return ret;
+
+ // X.509 fallback.
+ CHECK(BIO_reset(bp.get()));
+ return TryParsePublicKey(pkey, bp, "CERTIFICATE",
+ [](const unsigned char** p, long l) { // NOLINT(runtime/int)
+ X509Pointer x509(d2i_X509(nullptr, p, l));
+ return x509 ? X509_get_pubkey(x509.get()) : nullptr;
+ });
+}
+
+static bool ParsePublicKey(EVPKeyPointer* pkey,
+ const PublicKeyEncodingConfig& config,
+ const char* key,
+ size_t key_len,
+ bool allow_certificate) {
+ if (config.format_ == kKeyFormatPEM) {
+ ParsePublicKeyResult r =
+ ParsePublicKeyPEM(pkey, key, key_len, allow_certificate);
+ return r == ParsePublicKeyResult::kParsePublicOk;
+ } else {
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ pkey->reset(d2i_PublicKey(EVP_PKEY_RSA, nullptr, &p, key_len));
+ return pkey;
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSPKI);
+ pkey->reset(d2i_PUBKEY(nullptr, &p, key_len));
+ return pkey;
+ }
+ }
+}
+
+static inline Local<Value> BIOToStringOrBuffer(Environment* env,
+ BIO* bio,
+ PKFormatType format) {
+ BUF_MEM* bptr;
+ BIO_get_mem_ptr(bio, &bptr);
+ if (format == kKeyFormatPEM) {
+ // PEM is an ASCII format, so we will return it as a string.
+ return String::NewFromUtf8(env->isolate(), bptr->data,
+ NewStringType::kNormal,
+ bptr->length).ToLocalChecked();
+ } else {
+ CHECK_EQ(format, kKeyFormatDER);
+ // DER is binary, return it as a buffer.
+ return Buffer::Copy(env, bptr->data, bptr->length).ToLocalChecked();
+ }
+}
+
+static bool WritePublicKeyInner(EVP_PKEY* pkey,
+ const BIOPointer& bio,
+ const PublicKeyEncodingConfig& config) {
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ // 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 (config.format_ == kKeyFormatPEM) {
+ // Encode PKCS#1 as PEM.
+ return PEM_write_bio_RSAPublicKey(bio.get(), rsa.get()) == 1;
+ } else {
+ // Encode PKCS#1 as DER.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ return i2d_RSAPublicKey_bio(bio.get(), rsa.get()) == 1;
+ }
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSPKI);
+ if (config.format_ == kKeyFormatPEM) {
+ // Encode SPKI as PEM.
+ return PEM_write_bio_PUBKEY(bio.get(), pkey) == 1;
+ } else {
+ // Encode SPKI as DER.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ return i2d_PUBKEY_bio(bio.get(), pkey) == 1;
+ }
+ }
+}
+
+static MaybeLocal<Value> WritePublicKey(Environment* env,
+ EVP_PKEY* pkey,
+ const PublicKeyEncodingConfig& config) {
+ BIOPointer bio(BIO_new(BIO_s_mem()));
+ CHECK(bio);
+
+ if (!WritePublicKeyInner(pkey, bio, config)) {
+ ThrowCryptoError(env, ERR_get_error(), "Failed to encode public key");
+ return MaybeLocal<Value>();
+ }
+ return BIOToStringOrBuffer(env, bio.get(), config.format_);
+}
+
+static EVPKeyPointer ParsePrivateKey(const PrivateKeyEncodingConfig& config,
+ const char* key,
+ size_t key_len) {
+ EVPKeyPointer pkey;
+
+ if (config.format_ == kKeyFormatPEM) {
+ BIOPointer bio(BIO_new_mem_buf(key, key_len));
+ CHECK(bio);
+
+ char* pass = const_cast<char*>(config.passphrase_.get());
+ pkey.reset(PEM_read_bio_PrivateKey(bio.get(),
+ nullptr,
+ PasswordCallback,
+ pass));
+ } else {
+ CHECK_EQ(config.format_, kKeyFormatDER);
+
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
+ pkey.reset(d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &p, key_len));
+ } else if (config.type_.ToChecked() == kKeyEncodingPKCS8) {
+ BIOPointer bio(BIO_new_mem_buf(key, key_len));
+ CHECK(bio);
+ char* pass = const_cast<char*>(config.passphrase_.get());
+ pkey.reset(d2i_PKCS8PrivateKey_bio(bio.get(),
+ nullptr,
+ PasswordCallback,
+ pass));
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
+ const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
+ pkey.reset(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, key_len));
+ }
+ }
+
+ // OpenSSL can fail to parse the key but still return a non-null pointer.
+ if (ERR_peek_error() != 0)
+ pkey.reset();
+
+ return pkey;
+}
+
+ByteSource::ByteSource(ByteSource&& other)
+ : data_(other.data_),
+ allocated_data_(other.allocated_data_),
+ size_(other.size_) {
+ other.allocated_data_ = nullptr;
+}
+
+ByteSource::~ByteSource() {
+ OPENSSL_clear_free(allocated_data_, size_);
+}
+
+ByteSource& ByteSource::operator=(ByteSource&& other) {
+ if (&other != this) {
+ OPENSSL_clear_free(allocated_data_, size_);
+ data_ = other.data_;
+ allocated_data_ = other.allocated_data_;
+ other.allocated_data_ = nullptr;
+ size_ = other.size_;
+ }
+ return *this;
+}
+
+const char* ByteSource::get() const {
+ return data_;
+}
+
+size_t ByteSource::size() const {
+ return size_;
+}
+
+ByteSource ByteSource::FromStringOrBuffer(Environment* env,
+ Local<Value> value) {
+ return Buffer::HasInstance(value) ? FromBuffer(value)
+ : FromString(env, value.As<String>());
+}
+
+ByteSource ByteSource::FromString(Environment* env, Local<String> str,
+ bool ntc) {
+ CHECK(str->IsString());
+ size_t size = str->Utf8Length(env->isolate());
+ size_t alloc_size = ntc ? size + 1 : size;
+ char* data = MallocOpenSSL<char>(alloc_size);
+ int opts = String::NO_OPTIONS;
+ if (!ntc) opts |= String::NO_NULL_TERMINATION;
+ str->WriteUtf8(env->isolate(), data, alloc_size, nullptr, opts);
+ return Allocated(data, size);
+}
+
+ByteSource ByteSource::FromBuffer(Local<Value> buffer, bool ntc) {
+ size_t size = Buffer::Length(buffer);
+ if (ntc) {
+ char* data = MallocOpenSSL<char>(size + 1);
+ memcpy(data, Buffer::Data(buffer), size);
+ data[size] = 0;
+ return Allocated(data, size);
+ }
+ return Foreign(Buffer::Data(buffer), size);
+}
+
+ByteSource ByteSource::NullTerminatedCopy(Environment* env,
+ Local<Value> value) {
+ return Buffer::HasInstance(value) ? FromBuffer(value, true)
+ : FromString(env, value.As<String>(), true);
+}
+
+ByteSource ByteSource::FromSymmetricKeyObject(Local<Value> handle) {
+ CHECK(handle->IsObject());
+ KeyObject* key = Unwrap<KeyObject>(handle.As<Object>());
+ CHECK(key);
+ return Foreign(key->GetSymmetricKey(), key->GetSymmetricKeySize());
+}
+
+ByteSource::ByteSource(const char* data, char* allocated_data, size_t size)
+ : data_(data),
+ allocated_data_(allocated_data),
+ size_(size) {}
+
+ByteSource ByteSource::Allocated(char* data, size_t size) {
+ return ByteSource(data, data, size);
+}
+
+ByteSource ByteSource::Foreign(const char* data, size_t size) {
+ return ByteSource(data, nullptr, size);
+}
+
+enum KeyEncodingContext {
+ kKeyContextInput,
+ kKeyContextExport,
+ kKeyContextGenerate
+};
+
+static void GetKeyFormatAndTypeFromJs(
+ AsymmetricKeyEncodingConfig* config,
+ const FunctionCallbackInfo<Value>& args,
+ unsigned int* offset,
+ KeyEncodingContext context) {
+ // During key pair generation, it is possible not to specify a key encoding,
+ // which will lead to a key object being returned.
+ if (args[*offset]->IsUndefined()) {
+ CHECK_EQ(context, kKeyContextGenerate);
+ CHECK(args[*offset + 1]->IsUndefined());
+ config->output_key_object_ = true;
+ } else {
+ config->output_key_object_ = false;
+
+ CHECK(args[*offset]->IsInt32());
+ config->format_ = static_cast<PKFormatType>(
+ args[*offset].As<Int32>()->Value());
+
+ if (args[*offset + 1]->IsInt32()) {
+ config->type_ = Just<PKEncodingType>(static_cast<PKEncodingType>(
+ args[*offset + 1].As<Int32>()->Value()));
+ } else {
+ CHECK(context == kKeyContextInput && config->format_ == kKeyFormatPEM);
+ CHECK(args[*offset + 1]->IsNullOrUndefined());
+ config->type_ = Nothing<PKEncodingType>();
+ }
+ }
+
+ *offset += 2;
+}
+
+static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs(
+ const FunctionCallbackInfo<Value>& args,
+ unsigned int* offset,
+ KeyEncodingContext context) {
+ PublicKeyEncodingConfig result;
+ GetKeyFormatAndTypeFromJs(&result, args, offset, context);
+ return result;
+}
+
+static ManagedEVPPKey GetPublicKeyFromJs(
+ const FunctionCallbackInfo<Value>& args,
+ unsigned int* offset,
+ bool allow_key_object,
+ bool allow_certificate) {
+ if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
+ Environment* env = Environment::GetCurrent(args);
+ ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
+ PublicKeyEncodingConfig config =
+ GetPublicKeyEncodingFromJs(args, offset, kKeyContextInput);
+ EVPKeyPointer pkey;
+ ParsePublicKey(&pkey, config, key.get(), key.size(), allow_certificate);
+ if (!pkey)
+ ThrowCryptoError(env, ERR_get_error(), "Failed to read public key");
+ return ManagedEVPPKey(pkey.release());
+ } else {
+ CHECK(args[*offset]->IsObject() && allow_key_object);
+ KeyObject* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As<Object>(), ManagedEVPPKey());
+ CHECK_EQ(key->GetKeyType(), kKeyTypePublic);
+ (*offset) += 3;
+ return key->GetAsymmetricKey();
+ }
+}
+
+static NonCopyableMaybe<PrivateKeyEncodingConfig> GetPrivateKeyEncodingFromJs(
+ const FunctionCallbackInfo<Value>& args,
+ unsigned int* offset,
+ KeyEncodingContext context) {
+ Environment* env = Environment::GetCurrent(args);
+
+ PrivateKeyEncodingConfig result;
+ GetKeyFormatAndTypeFromJs(&result, args, offset, context);
+
+ if (result.output_key_object_) {
+ if (context != kKeyContextInput)
+ (*offset)++;
+ } else {
+ bool needs_passphrase = false;
+ if (context != kKeyContextInput) {
+ if (args[*offset]->IsString()) {
+ String::Utf8Value cipher_name(env->isolate(),
+ args[*offset].As<String>());
+ result.cipher_ = EVP_get_cipherbyname(*cipher_name);
+ if (result.cipher_ == nullptr) {
+ env->ThrowError("Unknown cipher");
+ return NonCopyableMaybe<PrivateKeyEncodingConfig>();
+ }
+ needs_passphrase = true;
+ } else {
+ CHECK(args[*offset]->IsNullOrUndefined());
+ result.cipher_ = nullptr;
+ }
+ (*offset)++;
+ }
+
+ if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
+ CHECK_IMPLIES(context != kKeyContextInput, result.cipher_ != nullptr);
+
+ result.passphrase_ = ByteSource::NullTerminatedCopy(env, args[*offset]);
+ } else {
+ CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
+ }
+ }
+
+ (*offset)++;
+ return NonCopyableMaybe<PrivateKeyEncodingConfig>(std::move(result));
+}
+
+static ManagedEVPPKey GetPrivateKeyFromJs(
+ const FunctionCallbackInfo<Value>& args,
+ unsigned int* offset,
+ bool allow_key_object) {
+ if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
+ Environment* env = Environment::GetCurrent(args);
+ ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
+ NonCopyableMaybe<PrivateKeyEncodingConfig> config =
+ GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput);
+ if (config.IsEmpty())
+ return ManagedEVPPKey();
+ EVPKeyPointer pkey =
+ ParsePrivateKey(config.Release(), key.get(), key.size());
+ if (!pkey)
+ ThrowCryptoError(env, ERR_get_error(), "Failed to read private key");
+ return ManagedEVPPKey(pkey.release());
+ } else {
+ CHECK(args[*offset]->IsObject() && allow_key_object);
+ KeyObject* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As<Object>(), ManagedEVPPKey());
+ CHECK_EQ(key->GetKeyType(), kKeyTypePrivate);
+ (*offset) += 4;
+ return key->GetAsymmetricKey();
+ }
+}
+
+static bool IsRSAPrivateKey(const unsigned char* data, size_t size) {
+ // Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE.
+ if (size >= 2 && data[0] == 0x30) {
+ size_t offset;
+ if (data[1] & 0x80) {
+ // Long form.
+ size_t n_bytes = data[1] & ~0x80;
+ if (n_bytes + 2 > size || n_bytes > sizeof(size_t))
+ return false;
+ size_t i, length = 0;
+ for (i = 0; i < n_bytes; i++)
+ length = (length << 8) | data[i + 2];
+ offset = 2 + n_bytes;
+ size = std::min(size, length + 2);
+ } else {
+ // Short form.
+ offset = 2;
+ size = std::min<size_t>(size, data[1] + 2);
+ }
+
+ // An RSAPrivateKey sequence always starts with a single-byte integer whose
+ // value is either 0 or 1, whereas an RSAPublicKey starts with the modulus
+ // (which is the product of two primes and therefore at least 4), so we can
+ // decide the type of the structure based on the first three bytes of the
+ // sequence.
+ return size - offset >= 3 &&
+ data[offset] == 2 &&
+ data[offset + 1] == 1 &&
+ !(data[offset + 2] & 0xfe);
+ }
+
+ return false;
+}
+
+static ManagedEVPPKey GetPublicOrPrivateKeyFromJs(
+ const FunctionCallbackInfo<Value>& args,
+ unsigned int* offset,
+ bool allow_key_object,
+ bool allow_certificate) {
+ if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
+ Environment* env = Environment::GetCurrent(args);
+ ByteSource data = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
+ NonCopyableMaybe<PrivateKeyEncodingConfig> config_ =
+ GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput);
+ if (config_.IsEmpty())
+ return ManagedEVPPKey();
+ PrivateKeyEncodingConfig config = config_.Release();
+ EVPKeyPointer pkey;
+ if (config.format_ == kKeyFormatPEM) {
+ // For PEM, we can easily determine whether it is a public or private key
+ // by looking for the respective PEM tags.
+ ParsePublicKeyResult ret = ParsePublicKeyPEM(&pkey, data.get(),
+ data.size(),
+ allow_certificate);
+ if (ret == ParsePublicKeyResult::kParsePublicNotRecognized) {
+ pkey = ParsePrivateKey(config, data.get(), data.size());
+ }
+ } else {
+ // For DER, the type determines how to parse it. SPKI, PKCS#8 and SEC1 are
+ // easy, but PKCS#1 can be a public key or a private key.
+ bool is_public;
+ switch (config.type_.ToChecked()) {
+ case kKeyEncodingPKCS1:
+ is_public = !IsRSAPrivateKey(
+ reinterpret_cast<const unsigned char*>(data.get()), data.size());
+ break;
+ case kKeyEncodingSPKI:
+ is_public = true;
+ break;
+ case kKeyEncodingPKCS8:
+ case kKeyEncodingSEC1:
+ is_public = false;
+ break;
+ default:
+ CHECK(!"Invalid key encoding type");
+ }
+
+ if (is_public) {
+ ParsePublicKey(&pkey, config, data.get(), data.size(),
+ allow_certificate);
+ } else {
+ pkey = ParsePrivateKey(config, data.get(), data.size());
+ }
+ }
+ if (!pkey)
+ ThrowCryptoError(env, ERR_get_error(), "Failed to read asymmetric key");
+ return ManagedEVPPKey(pkey.release());
+ } else {
+ CHECK(args[*offset]->IsObject() && allow_key_object);
+ KeyObject* key = Unwrap<KeyObject>(args[*offset].As<Object>());
+ CHECK(key);
+ CHECK_NE(key->GetKeyType(), kKeyTypeSecret);
+ (*offset) += 4;
+ return key->GetAsymmetricKey();
+ }
+}
+
+static MaybeLocal<Value> WritePrivateKey(
+ Environment* env,
+ EVP_PKEY* pkey,
+ const PrivateKeyEncodingConfig& config) {
+ BIOPointer bio(BIO_new(BIO_s_mem()));
+ CHECK(bio);
+
+ bool err;
+
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ // PKCS#1 is only permitted for RSA keys.
+ CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
+
+ RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
+ if (config.format_ == kKeyFormatPEM) {
+ // Encode PKCS#1 as PEM.
+ const char* pass = config.passphrase_.get();
+ err = PEM_write_bio_RSAPrivateKey(
+ bio.get(), rsa.get(),
+ config.cipher_,
+ reinterpret_cast<unsigned char*>(const_cast<char*>(pass)),
+ config.passphrase_.size(),
+ nullptr, nullptr) != 1;
+ } else {
+ // Encode PKCS#1 as DER. This does not permit encryption.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ CHECK_NULL(config.cipher_);
+ err = i2d_RSAPrivateKey_bio(bio.get(), rsa.get()) != 1;
+ }
+ } else if (config.type_.ToChecked() == kKeyEncodingPKCS8) {
+ if (config.format_ == kKeyFormatPEM) {
+ // Encode PKCS#8 as PEM.
+ err = PEM_write_bio_PKCS8PrivateKey(
+ bio.get(), pkey,
+ config.cipher_,
+ const_cast<char*>(config.passphrase_.get()),
+ config.passphrase_.size(),
+ nullptr, nullptr) != 1;
+ } else {
+ // Encode PKCS#8 as DER.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ err = i2d_PKCS8PrivateKey_bio(
+ bio.get(), pkey,
+ config.cipher_,
+ const_cast<char*>(config.passphrase_.get()),
+ config.passphrase_.size(),
+ nullptr, nullptr) != 1;
+ }
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
+
+ // SEC1 is only permitted for EC keys.
+ CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_EC);
+
+ ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey));
+ if (config.format_ == kKeyFormatPEM) {
+ // Encode SEC1 as PEM.
+ const char* pass = config.passphrase_.get();
+ err = PEM_write_bio_ECPrivateKey(
+ bio.get(), ec_key.get(),
+ config.cipher_,
+ reinterpret_cast<unsigned char*>(const_cast<char*>(pass)),
+ config.passphrase_.size(),
+ nullptr, nullptr) != 1;
+ } else {
+ // Encode SEC1 as DER. This does not permit encryption.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ CHECK_NULL(config.cipher_);
+ err = i2d_ECPrivateKey_bio(bio.get(), ec_key.get()) != 1;
+ }
+ }
+
+ if (err) {
+ ThrowCryptoError(env, ERR_get_error(), "Failed to encode private key");
+ return MaybeLocal<Value>();
+ }
+ return BIOToStringOrBuffer(env, bio.get(), config.format_);
+}
+
+ManagedEVPPKey::ManagedEVPPKey() : pkey_(nullptr) {}
+
+ManagedEVPPKey::ManagedEVPPKey(EVP_PKEY* pkey) : pkey_(pkey) {}
+
+ManagedEVPPKey::ManagedEVPPKey(const ManagedEVPPKey& key) : pkey_(nullptr) {
+ *this = key;
+}
+
+ManagedEVPPKey::ManagedEVPPKey(ManagedEVPPKey&& key) {
+ *this = key;
+}
+
+ManagedEVPPKey::~ManagedEVPPKey() {
+ EVP_PKEY_free(pkey_);
+}
+
+ManagedEVPPKey& ManagedEVPPKey::operator=(const ManagedEVPPKey& key) {
+ EVP_PKEY_free(pkey_);
+ pkey_ = key.pkey_;
+ EVP_PKEY_up_ref(pkey_);
+ return *this;
+}
+
+ManagedEVPPKey& ManagedEVPPKey::operator=(ManagedEVPPKey&& key) {
+ EVP_PKEY_free(pkey_);
+ pkey_ = key.pkey_;
+ key.pkey_ = nullptr;
+ return *this;
+}
+
+ManagedEVPPKey::operator bool() const {
+ return pkey_ != nullptr;
+}
+
+EVP_PKEY* ManagedEVPPKey::get() const {
+ return pkey_;
+}
+
+Local<Function> KeyObject::Initialize(Environment* env, Local<Object> target) {
+ Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+
+ env->SetProtoMethod(t, "init", Init);
+ env->SetProtoMethodNoSideEffect(t, "getSymmetricKeySize",
+ GetSymmetricKeySize);
+ env->SetProtoMethodNoSideEffect(t, "getAsymmetricKeyType",
+ GetAsymmetricKeyType);
+ env->SetProtoMethod(t, "export", Export);
+
+ target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObject"),
+ t->GetFunction(env->context()).ToLocalChecked());
+
+ return t->GetFunction();
+}
+
+Local<Object> KeyObject::Create(Environment* env,
+ KeyType key_type,
+ const ManagedEVPPKey& pkey) {
+ CHECK_NE(key_type, kKeyTypeSecret);
+ Local<Value> type = Integer::New(env->isolate(), key_type);
+ Local<Object> obj =
+ env->crypto_key_object_constructor()->NewInstance(env->context(),
+ 1, &type)
+ .ToLocalChecked();
+ KeyObject* key = Unwrap<KeyObject>(obj);
+ CHECK(key);
+ if (key_type == kKeyTypePublic)
+ key->InitPublic(pkey);
+ else
+ key->InitPrivate(pkey);
+ return obj;
+}
+
+ManagedEVPPKey KeyObject::GetAsymmetricKey() const {
+ CHECK_NE(key_type_, kKeyTypeSecret);
+ return this->asymmetric_key_;
+}
+
+const char* KeyObject::GetSymmetricKey() const {
+ CHECK_EQ(key_type_, kKeyTypeSecret);
+ return this->symmetric_key_.get();
+}
+
+size_t KeyObject::GetSymmetricKeySize() const {
+ CHECK_EQ(key_type_, kKeyTypeSecret);
+ return this->symmetric_key_len_;
+}
+
+void KeyObject::New(const FunctionCallbackInfo<Value>& args) {
+ CHECK(args.IsConstructCall());
+ CHECK(args[0]->IsInt32());
+ KeyType key_type = static_cast<KeyType>(args[0].As<Uint32>()->Value());
+ Environment* env = Environment::GetCurrent(args);
+ new KeyObject(env, args.This(), key_type);
+}
+
+KeyType KeyObject::GetKeyType() const {
+ return this->key_type_;
+}
+
+void KeyObject::Init(const FunctionCallbackInfo<Value>& args) {
+ KeyObject* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
+
+ unsigned int offset;
+ ManagedEVPPKey pkey;
+
+ switch (key->key_type_) {
+ case kKeyTypeSecret:
+ CHECK_EQ(args.Length(), 1);
+ key->InitSecret(Buffer::Data(args[0]), Buffer::Length(args[0]));
+ break;
+ case kKeyTypePublic:
+ CHECK_EQ(args.Length(), 3);
+
+ offset = 0;
+ pkey = GetPublicKeyFromJs(args, &offset, false, false);
+ if (!pkey)
+ return;
+ key->InitPublic(pkey);
+ break;
+ case kKeyTypePrivate:
+ CHECK_EQ(args.Length(), 4);
+
+ offset = 0;
+ pkey = GetPrivateKeyFromJs(args, &offset, false);
+ if (!pkey)
+ return;
+ key->InitPrivate(pkey);
+ break;
+ default:
+ CHECK(false);
+ }
+}
+
+void KeyObject::InitSecret(const char* key, size_t key_len) {
+ CHECK_EQ(this->key_type_, kKeyTypeSecret);
+
+ char* mem = MallocOpenSSL<char>(key_len);
+ memcpy(mem, key, key_len);
+ this->symmetric_key_ = std::unique_ptr<char, std::function<void(char*)>>(mem,
+ [key_len](char* p) {
+ OPENSSL_clear_free(p, key_len);
+ });
+ this->symmetric_key_len_ = key_len;
+}
+
+void KeyObject::InitPublic(const ManagedEVPPKey& pkey) {
+ CHECK_EQ(this->key_type_, kKeyTypePublic);
+ CHECK(pkey);
+ this->asymmetric_key_ = pkey;
+}
+
+void KeyObject::InitPrivate(const ManagedEVPPKey& pkey) {
+ CHECK_EQ(this->key_type_, kKeyTypePrivate);
+ CHECK(pkey);
+ this->asymmetric_key_ = pkey;
+}
+
+Local<String> KeyObject::GetAsymmetricKeyType() const {
+ CHECK_NE(this->key_type_, kKeyTypeSecret);
+ switch (EVP_PKEY_id(this->asymmetric_key_.get())) {
+ case EVP_PKEY_RSA:
+ return env()->crypto_rsa_string();
+ case EVP_PKEY_DSA:
+ return env()->crypto_dsa_string();
+ case EVP_PKEY_EC:
+ return env()->crypto_ec_string();
+ default:
+ CHECK(false);
+ }
+}
+
+void KeyObject::GetAsymmetricKeyType(const FunctionCallbackInfo<Value>& args) {
+ KeyObject* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
+
+ args.GetReturnValue().Set(key->GetAsymmetricKeyType());
+}
+
+void KeyObject::GetSymmetricKeySize(const FunctionCallbackInfo<Value>& args) {
+ KeyObject* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
+ args.GetReturnValue().Set(static_cast<uint32_t>(key->GetSymmetricKeySize()));
+}
+
+void KeyObject::Export(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ KeyObject* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
+
+ MaybeLocal<Value> result;
+ if (key->key_type_ == kKeyTypeSecret) {
+ result = key->ExportSecretKey();
+ } else if (key->key_type_ == kKeyTypePublic) {
+ unsigned int offset = 0;
+ PublicKeyEncodingConfig config =
+ GetPublicKeyEncodingFromJs(args, &offset, kKeyContextExport);
+ CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
+ result = key->ExportPublicKey(config);
+ } else {
+ CHECK_EQ(key->key_type_, kKeyTypePrivate);
+ unsigned int offset = 0;
+ NonCopyableMaybe<PrivateKeyEncodingConfig> config =
+ GetPrivateKeyEncodingFromJs(args, &offset, kKeyContextExport);
+ if (config.IsEmpty())
+ return;
+ CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
+ result = key->ExportPrivateKey(config.Release());
+ }
+
+ if (!result.IsEmpty())
+ args.GetReturnValue().Set(result.ToLocalChecked());
+}
+
+Local<Value> KeyObject::ExportSecretKey() const {
+ return Buffer::Copy(env(), symmetric_key_.get(), symmetric_key_len_)
+ .ToLocalChecked();
+}
+
+MaybeLocal<Value> KeyObject::ExportPublicKey(
+ const PublicKeyEncodingConfig& config) const {
+ return WritePublicKey(env(), asymmetric_key_.get(), config);
+}
+
+MaybeLocal<Value> KeyObject::ExportPrivateKey(
+ const PrivateKeyEncodingConfig& config) const {
+ return WritePrivateKey(env(), asymmetric_key_.get(), config);
+}
+
+
void CipherBase::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
@@ -2864,6 +3696,15 @@ void CipherBase::InitIv(const char* cipher_type,
}
+static ByteSource GetSecretKeyBytes(Environment* env, Local<Value> value) {
+ // A key can be passed as a string, buffer or KeyObject with type 'secret'.
+ // If it is a string, we need to convert it to a buffer. We are not doing that
+ // in JS to avoid creating an unprotected copy on the heap.
+ return value->IsString() || Buffer::HasInstance(value) ?
+ ByteSource::FromStringOrBuffer(env, value) :
+ ByteSource::FromSymmetricKeyObject(value);
+}
+
void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
@@ -2872,9 +3713,8 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
CHECK_GE(args.Length(), 4);
const node::Utf8Value cipher_type(env->isolate(), args[0]);
- ssize_t key_len = Buffer::Length(args[1]);
- const unsigned char* key_buf = reinterpret_cast<unsigned char*>(
- Buffer::Data(args[1]));
+ const ByteSource key = GetSecretKeyBytes(env, args[1]);
+
ssize_t iv_len;
const unsigned char* iv_buf;
if (args[2]->IsNull()) {
@@ -2895,7 +3735,12 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
auth_tag_len = kNoAuthTagLength;
}
- cipher->InitIv(*cipher_type, key_buf, key_len, iv_buf, iv_len, auth_tag_len);
+ cipher->InitIv(*cipher_type,
+ reinterpret_cast<const unsigned char*>(key.get()),
+ key.size(),
+ iv_buf,
+ iv_len,
+ auth_tag_len);
}
@@ -3351,9 +4196,8 @@ void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = hmac->env();
const node::Utf8Value hash_type(env->isolate(), args[0]);
- const char* buffer_data = Buffer::Data(args[1]);
- size_t buffer_length = Buffer::Length(args[1]);
- hmac->HmacInit(*hash_type, buffer_data, buffer_length);
+ ByteSource key = GetSecretKeyBytes(env, args[1]);
+ hmac->HmacInit(*hash_type, key.get(), key.size());
}
@@ -3602,7 +4446,7 @@ void SignBase::CheckThrow(SignBase::Error error) {
}
}
-static bool ApplyRSAOptions(const EVPKeyPointer& pkey,
+static bool ApplyRSAOptions(const ManagedEVPPKey& pkey,
EVP_PKEY_CTX* pkctx,
int padding,
int salt_len) {
@@ -3664,7 +4508,7 @@ void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
}
static MallocedBuffer<unsigned char> Node_SignFinal(EVPMDPointer&& mdctx,
- const EVPKeyPointer& pkey,
+ const ManagedEVPPKey& pkey,
int padding,
int pss_salt_len) {
unsigned char m[EVP_MAX_MD_SIZE];
@@ -3693,9 +4537,7 @@ static MallocedBuffer<unsigned char> Node_SignFinal(EVPMDPointer&& mdctx,
}
Sign::SignResult Sign::SignFinal(
- const char* key_pem,
- int key_pem_len,
- const char* passphrase,
+ const ManagedEVPPKey& pkey,
int padding,
int salt_len) {
if (!mdctx_)
@@ -3703,21 +4545,6 @@ Sign::SignResult Sign::SignFinal(
EVPMDPointer mdctx = std::move(mdctx_);
- BIOPointer bp(BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len));
- if (!bp)
- return SignResult(kSignPrivateKey);
-
- EVPKeyPointer pkey(PEM_read_bio_PrivateKey(bp.get(),
- nullptr,
- PasswordCallback,
- const_cast<char*>(passphrase)));
-
- // Errors might be injected into OpenSSL's error stack
- // without `pkey` being set to nullptr;
- // cf. the test of `test_bad_rsa_privkey.pem` for an example.
- if (!pkey || 0 != ERR_peek_error())
- return SignResult(kSignPrivateKey);
-
#ifdef NODE_FIPS_MODE
/* Validate DSA2 parameters from FIPS 186-4 */
if (FIPS_mode() && EVP_PKEY_DSA == pkey->type) {
@@ -3753,25 +4580,21 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder());
- unsigned int len = args.Length();
-
- node::Utf8Value passphrase(env->isolate(), args[1]);
-
- size_t buf_len = Buffer::Length(args[0]);
- char* buf = Buffer::Data(args[0]);
+ unsigned int offset = 0;
+ ManagedEVPPKey key = GetPrivateKeyFromJs(args, &offset, true);
+ if (!key)
+ return;
- CHECK(args[2]->IsInt32());
- int padding = args[2].As<Int32>()->Value();
+ CHECK(args[offset]->IsInt32());
+ int padding = args[offset].As<Int32>()->Value();
- CHECK(args[3]->IsInt32());
- int salt_len = args[3].As<Int32>()->Value();
+ CHECK(args[offset + 1]->IsInt32());
+ int salt_len = args[offset + 1].As<Int32>()->Value();
ClearErrorOnReturn clear_error_on_return;
SignResult ret = sign->SignFinal(
- buf,
- buf_len,
- len >= 2 && !args[1]->IsNull() ? *passphrase : nullptr,
+ key,
padding,
salt_len);
@@ -3787,72 +4610,6 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(rc);
}
-enum ParsePublicKeyResult {
- kParsePublicOk,
- kParsePublicNotRecognized,
- kParsePublicFailed
-};
-
-static ParsePublicKeyResult TryParsePublicKey(
- EVPKeyPointer* pkey,
- const BIOPointer& bp,
- const char* name,
- // NOLINTNEXTLINE(runtime/int)
- std::function<EVP_PKEY*(const unsigned char** p, long l)> parse) {
- unsigned char* der_data;
- long der_len; // NOLINT(runtime/int)
-
- // This skips surrounding data and decodes PEM to DER.
- {
- MarkPopErrorOnReturn mark_pop_error_on_return;
- if (PEM_bytes_read_bio(&der_data, &der_len, nullptr, name,
- bp.get(), nullptr, nullptr) != 1)
- return kParsePublicNotRecognized;
- }
-
- // OpenSSL might modify the pointer, so we need to make a copy before parsing.
- const unsigned char* p = der_data;
- pkey->reset(parse(&p, der_len));
- OPENSSL_clear_free(der_data, der_len);
-
- return *pkey ? kParsePublicOk : kParsePublicFailed;
-}
-
-static ParsePublicKeyResult ParsePublicKey(EVPKeyPointer* pkey,
- const char* key_pem,
- int key_pem_len) {
- BIOPointer bp(BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len));
- if (!bp)
- return kParsePublicFailed;
-
- ParsePublicKeyResult ret;
-
- // Try PKCS#8 first.
- ret = TryParsePublicKey(pkey, bp, "PUBLIC KEY",
- [](const unsigned char** p, long l) { // NOLINT(runtime/int)
- return d2i_PUBKEY(nullptr, p, l);
- });
- if (ret != kParsePublicNotRecognized)
- return ret;
-
- // Maybe it is PKCS#1.
- CHECK(BIO_reset(bp.get()));
- ret = TryParsePublicKey(pkey, bp, "RSA PUBLIC KEY",
- [](const unsigned char** p, long l) { // NOLINT(runtime/int)
- return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l);
- });
- if (ret != kParsePublicNotRecognized)
- return ret;
-
- // X.509 fallback.
- CHECK(BIO_reset(bp.get()));
- return TryParsePublicKey(pkey, bp, "CERTIFICATE",
- [](const unsigned char** p, long l) { // NOLINT(runtime/int)
- X509Pointer x509(d2i_X509(nullptr, p, l));
- return x509 ? X509_get_pubkey(x509.get()) : nullptr;
- });
-}
-
void Verify::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
@@ -3896,8 +4653,7 @@ void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
}
-SignBase::Error Verify::VerifyFinal(const char* key_pem,
- int key_pem_len,
+SignBase::Error Verify::VerifyFinal(const ManagedEVPPKey& pkey,
const char* sig,
int siglen,
int padding,
@@ -3906,15 +4662,11 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
if (!mdctx_)
return kSignNotInitialised;
- EVPKeyPointer pkey;
unsigned char m[EVP_MAX_MD_SIZE];
unsigned int m_len;
*verify_result = false;
EVPMDPointer mdctx = std::move(mdctx_);
- if (ParsePublicKey(&pkey, key_pem, key_pem_len) != kParsePublicOk)
- return kSignPublicKey;
-
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len))
return kSignPublicKey;
@@ -3942,20 +4694,20 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder());
- char* kbuf = Buffer::Data(args[0]);
- ssize_t klen = Buffer::Length(args[0]);
+ unsigned int offset = 0;
+ ManagedEVPPKey pkey = GetPublicKeyFromJs(args, &offset, true, true);
- char* hbuf = Buffer::Data(args[1]);
- ssize_t hlen = Buffer::Length(args[1]);
+ char* hbuf = Buffer::Data(args[offset]);
+ ssize_t hlen = Buffer::Length(args[offset]);
- CHECK(args[2]->IsInt32());
- int padding = args[2].As<Int32>()->Value();
+ CHECK(args[offset + 1]->IsInt32());
+ int padding = args[offset + 1].As<Int32>()->Value();
- CHECK(args[3]->IsInt32());
- int salt_len = args[3].As<Int32>()->Value();
+ CHECK(args[offset + 2]->IsInt32());
+ int salt_len = args[offset + 2].As<Int32>()->Value();
bool verify_result;
- Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, padding, salt_len,
+ Error err = verify->VerifyFinal(pkey, hbuf, hlen, padding, salt_len,
&verify_result);
if (err != kSignOk)
return verify->CheckThrow(err);
@@ -3966,36 +4718,12 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
template <PublicKeyCipher::Operation operation,
PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
-bool PublicKeyCipher::Cipher(const char* key_pem,
- int key_pem_len,
- const char* passphrase,
+bool PublicKeyCipher::Cipher(const ManagedEVPPKey& pkey,
int padding,
const unsigned char* data,
int len,
unsigned char** out,
size_t* out_len) {
- EVPKeyPointer pkey;
-
- // Check if this is a PKCS#8 or RSA public key before trying as X.509 and
- // private key.
- if (operation == kPublic) {
- ParsePublicKeyResult pkeyres = ParsePublicKey(&pkey, key_pem, key_pem_len);
- if (pkeyres == kParsePublicFailed)
- return false;
- }
- if (!pkey) {
- // Private key fallback.
- BIOPointer bp(BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len));
- if (!bp)
- return false;
- pkey.reset(PEM_read_bio_PrivateKey(bp.get(),
- nullptr,
- PasswordCallback,
- const_cast<char*>(passphrase)));
- if (!pkey)
- return false;
- }
-
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
if (!ctx)
return false;
@@ -4022,18 +4750,17 @@ template <PublicKeyCipher::Operation operation,
void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Key");
- char* kbuf = Buffer::Data(args[0]);
- ssize_t klen = Buffer::Length(args[0]);
+ unsigned int offset = 0;
+ ManagedEVPPKey pkey = GetPublicOrPrivateKeyFromJs(args, &offset, true, true);
+ if (!pkey)
+ return;
- THROW_AND_RETURN_IF_NOT_BUFFER(env, args[1], "Data");
- char* buf = Buffer::Data(args[1]);
- ssize_t len = Buffer::Length(args[1]);
+ THROW_AND_RETURN_IF_NOT_BUFFER(env, args[offset], "Data");
+ char* buf = Buffer::Data(args[offset]);
+ ssize_t len = Buffer::Length(args[offset]);
uint32_t padding;
- if (!args[2]->Uint32Value(env->context()).To(&padding)) return;
-
- String::Utf8Value passphrase(args.GetIsolate(), args[3]);
+ if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return;
unsigned char* out_value = nullptr;
size_t out_len = 0;
@@ -4041,9 +4768,7 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
ClearErrorOnReturn clear_error_on_return;
bool r = Cipher<operation, EVP_PKEY_cipher_init, EVP_PKEY_cipher>(
- kbuf,
- klen,
- args.Length() >= 4 && !args[3]->IsNull() ? *passphrase : nullptr,
+ pkey,
padding,
reinterpret_cast<const unsigned char*>(buf),
len,
@@ -5048,46 +5773,17 @@ class ECKeyPairGenerationConfig : public KeyPairGenerationConfig {
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)
+ PrivateKeyEncodingConfig&& private_key_encoding)
: CryptoJob(env),
config_(std::move(config)),
public_key_encoding_(public_key_encoding),
- private_key_encoding_(private_key_encoding),
+ private_key_encoding_(std::forward<PrivateKeyEncodingConfig>(
+ private_key_encoding)),
pkey_(nullptr) {}
inline void DoThreadPoolWork() override {
@@ -5116,7 +5812,7 @@ class GenerateKeyPairJob : public CryptoJob {
EVP_PKEY* pkey = nullptr;
if (EVP_PKEY_keygen(ctx.get(), &pkey) != 1)
return false;
- pkey_.reset(pkey);
+ pkey_ = ManagedEVPPKey(pkey);
return true;
}
@@ -5143,197 +5839,59 @@ class GenerateKeyPairJob : public CryptoJob {
}
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;
- }
+ if (public_key_encoding_.output_key_object_) {
+ // Note that this has the downside of containing sensitive data of the
+ // private key.
+ *pubkey = KeyObject::Create(env, kKeyTypePublic, pkey_);
} 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;
- }
+ MaybeLocal<Value> maybe_pubkey =
+ WritePublicKey(env, pkey_.get(), public_key_encoding_);
+ if (maybe_pubkey.IsEmpty())
+ return false;
+ *pubkey = maybe_pubkey.ToLocalChecked();
}
- // 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.
- CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
-
- RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
- if (private_key_encoding_.format_ == PK_FORMAT_PEM) {
- // Encode PKCS#1 as PEM.
- char* pass = private_key_encoding_.passphrase_.get();
- if (PEM_write_bio_RSAPrivateKey(
- bio.get(), rsa.get(),
- private_key_encoding_.cipher_,
- reinterpret_cast<unsigned char*>(pass),
- private_key_encoding_.passphrase_length_,
- nullptr, nullptr) != 1)
- return false;
- } else {
- // Encode PKCS#1 as DER. This does not permit encryption.
- CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER);
- CHECK_NULL(private_key_encoding_.cipher_);
- 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;
- }
+ // Now do the same for the private key.
+ if (private_key_encoding_.output_key_object_) {
+ *privkey = KeyObject::Create(env, kKeyTypePrivate, pkey_);
} else {
- CHECK_EQ(private_key_encoding_.type_, PK_ENCODING_SEC1);
-
- // SEC1 is only permitted for EC keys.
- CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_EC);
-
- ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey));
- if (private_key_encoding_.format_ == PK_FORMAT_PEM) {
- // Encode SEC1 as PEM.
- char* pass = private_key_encoding_.passphrase_.get();
- if (PEM_write_bio_ECPrivateKey(
- bio.get(), ec_key.get(),
- private_key_encoding_.cipher_,
- reinterpret_cast<unsigned char*>(pass),
- private_key_encoding_.passphrase_length_,
- nullptr, nullptr) != 1)
- return false;
- } else {
- // Encode SEC1 as DER. This does not permit encryption.
- CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER);
- CHECK_NULL(private_key_encoding_.cipher_);
- if (i2d_ECPrivateKey_bio(bio.get(), ec_key.get()) != 1)
- return false;
- }
+ MaybeLocal<Value> maybe_privkey =
+ WritePrivateKey(env, pkey_.get(), private_key_encoding_);
+ if (maybe_privkey.IsEmpty())
+ return false;
+ *privkey = maybe_privkey.ToLocalChecked();
}
- 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_;
+ ManagedEVPPKey pkey_;
};
void GenerateKeyPair(const FunctionCallbackInfo<Value>& args,
- unsigned int n_opts,
+ unsigned int offset,
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;
- }
+
+ PublicKeyEncodingConfig public_key_encoding =
+ GetPublicKeyEncodingFromJs(args, &offset, kKeyContextGenerate);
+ NonCopyableMaybe<PrivateKeyEncodingConfig> private_key_encoding =
+ GetPrivateKeyEncodingFromJs(args, &offset, kKeyContextGenerate);
+
+ if (private_key_encoding.IsEmpty())
+ return;
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]);
+ private_key_encoding.Release()));
+ if (args[offset]->IsObject())
+ return GenerateKeyPairJob::Run(std::move(job), args[offset]);
env->PrintSyncTrace();
job->DoThreadPoolWork();
Local<Value> err, pubkey, privkey;
@@ -5778,6 +6336,7 @@ void Initialize(Local<Object> target,
Environment* env = Environment::GetCurrent(context);
SecureContext::Initialize(env, target);
+ env->set_crypto_key_object_constructor(KeyObject::Initialize(env, target));
CipherBase::Initialize(env, target);
DiffieHellman::Initialize(env, target);
ECDH::Initialize(env, target);
@@ -5809,12 +6368,15 @@ void Initialize(Local<Object> target,
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);
+ NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
+ NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
+ NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
+ NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
+ NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
+ NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
+ NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
+ NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
+ NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
env->SetMethod(target, "randomBytes", RandomBytes);
env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
diff --git a/src/node_crypto.h b/src/node_crypto.h
index dd22c9eae4..1b950846a7 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -340,6 +340,163 @@ class SSLWrap {
friend class SecureContext;
};
+// A helper class representing a read-only byte array. When deallocated, its
+// contents are zeroed.
+class ByteSource {
+ public:
+ ByteSource() = default;
+ ByteSource(ByteSource&& other);
+ ~ByteSource();
+
+ ByteSource& operator=(ByteSource&& other);
+
+ const char* get() const;
+ size_t size() const;
+
+ static ByteSource FromStringOrBuffer(Environment* env,
+ v8::Local<v8::Value> value);
+
+ static ByteSource FromString(Environment* env,
+ v8::Local<v8::String> str,
+ bool ntc = false);
+
+ static ByteSource FromBuffer(v8::Local<v8::Value> buffer,
+ bool ntc = false);
+
+ static ByteSource NullTerminatedCopy(Environment* env,
+ v8::Local<v8::Value> value);
+
+ static ByteSource FromSymmetricKeyObject(v8::Local<v8::Value> handle);
+
+ private:
+ const char* data_ = nullptr;
+ char* allocated_data_ = nullptr;
+ size_t size_ = 0;
+
+ ByteSource(const char* data, char* allocated_data, size_t size);
+
+ static ByteSource Allocated(char* data, size_t size);
+ static ByteSource Foreign(const char* data, size_t size);
+
+ DISALLOW_COPY_AND_ASSIGN(ByteSource);
+};
+
+enum PKEncodingType {
+ // RSAPublicKey / RSAPrivateKey according to PKCS#1.
+ kKeyEncodingPKCS1,
+ // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
+ kKeyEncodingPKCS8,
+ // SubjectPublicKeyInfo according to X.509.
+ kKeyEncodingSPKI,
+ // ECPrivateKey according to SEC1.
+ kKeyEncodingSEC1
+};
+
+enum PKFormatType {
+ kKeyFormatDER,
+ kKeyFormatPEM
+};
+
+struct AsymmetricKeyEncodingConfig {
+ bool output_key_object_;
+ PKFormatType format_;
+ v8::Maybe<PKEncodingType> type_ = v8::Nothing<PKEncodingType>();
+};
+
+typedef AsymmetricKeyEncodingConfig PublicKeyEncodingConfig;
+
+struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig {
+ const EVP_CIPHER* cipher_;
+ ByteSource passphrase_;
+};
+
+enum KeyType {
+ kKeyTypeSecret,
+ kKeyTypePublic,
+ kKeyTypePrivate
+};
+
+// This uses the built-in reference counter of OpenSSL to manage an EVP_PKEY
+// which is slightly more efficient than using a shared pointer and easier to
+// use.
+class ManagedEVPPKey {
+ public:
+ ManagedEVPPKey();
+ explicit ManagedEVPPKey(EVP_PKEY* pkey);
+ ManagedEVPPKey(const ManagedEVPPKey& key);
+ ManagedEVPPKey(ManagedEVPPKey&& key);
+ ~ManagedEVPPKey();
+
+ ManagedEVPPKey& operator=(const ManagedEVPPKey& key);
+ ManagedEVPPKey& operator=(ManagedEVPPKey&& key);
+
+ operator bool() const;
+ EVP_PKEY* get() const;
+
+ private:
+ EVP_PKEY* pkey_;
+};
+
+class KeyObject : public BaseObject {
+ public:
+ static v8::Local<v8::Function> Initialize(Environment* env,
+ v8::Local<v8::Object> target);
+
+ static v8::Local<v8::Object> Create(Environment* env,
+ KeyType type,
+ const ManagedEVPPKey& pkey);
+
+ // TODO(tniessen): track the memory used by OpenSSL types
+ SET_NO_MEMORY_INFO()
+ SET_MEMORY_INFO_NAME(KeyObject)
+ SET_SELF_SIZE(KeyObject)
+
+ KeyType GetKeyType() const;
+
+ // These functions allow unprotected access to the raw key material and should
+ // only be used to implement cryptograohic operations requiring the key.
+ ManagedEVPPKey GetAsymmetricKey() const;
+ const char* GetSymmetricKey() const;
+ size_t GetSymmetricKeySize() const;
+
+ protected:
+ static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void InitSecret(const char* key, size_t key_len);
+ void InitPublic(const ManagedEVPPKey& pkey);
+ void InitPrivate(const ManagedEVPPKey& pkey);
+
+ static void GetAsymmetricKeyType(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ v8::Local<v8::String> GetAsymmetricKeyType() const;
+
+ static void GetSymmetricKeySize(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ static void Export(const v8::FunctionCallbackInfo<v8::Value>& args);
+ v8::Local<v8::Value> ExportSecretKey() const;
+ v8::MaybeLocal<v8::Value> ExportPublicKey(
+ const PublicKeyEncodingConfig& config) const;
+ v8::MaybeLocal<v8::Value> ExportPrivateKey(
+ const PrivateKeyEncodingConfig& config) const;
+
+ KeyObject(Environment* env,
+ v8::Local<v8::Object> wrap,
+ KeyType key_type)
+ : BaseObject(env, wrap),
+ key_type_(key_type),
+ symmetric_key_(nullptr, nullptr) {
+ MakeWeak();
+ }
+
+ private:
+ const KeyType key_type_;
+ std::unique_ptr<char, std::function<void(char*)>> symmetric_key_;
+ unsigned int symmetric_key_len_;
+ ManagedEVPPKey asymmetric_key_;
+};
+
class CipherBase : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
@@ -528,9 +685,7 @@ class Sign : public SignBase {
};
SignResult SignFinal(
- const char* key_pem,
- int key_pem_len,
- const char* passphrase,
+ const ManagedEVPPKey& pkey,
int padding,
int saltlen);
@@ -549,8 +704,7 @@ class Verify : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
- Error VerifyFinal(const char* key_pem,
- int key_pem_len,
+ Error VerifyFinal(const ManagedEVPPKey& key,
const char* sig,
int siglen,
int padding,
@@ -583,9 +737,7 @@ class PublicKeyCipher {
template <Operation operation,
EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
EVP_PKEY_cipher_t EVP_PKEY_cipher>
- static bool Cipher(const char* key_pem,
- int key_pem_len,
- const char* passphrase,
+ static bool Cipher(const ManagedEVPPKey& pkey,
int padding,
const unsigned char* data,
int len,
diff --git a/src/util.h b/src/util.h
index 9a10904394..36a2ec9e3a 100644
--- a/src/util.h
+++ b/src/util.h
@@ -473,6 +473,29 @@ struct MallocedBuffer {
MallocedBuffer& operator=(const MallocedBuffer&) = delete;
};
+template <typename T>
+class NonCopyableMaybe {
+ public:
+ NonCopyableMaybe() : empty_(true) {}
+ explicit NonCopyableMaybe(T&& value)
+ : empty_(false),
+ value_(std::move(value)) {}
+
+ bool IsEmpty() const {
+ return empty_;
+ }
+
+ T&& Release() {
+ CHECK_EQ(empty_, false);
+ empty_ = true;
+ return std::move(value_);
+ }
+
+ private:
+ bool empty_;
+ T value_;
+};
+
// Test whether some value can be called with ().
template <typename T, typename = void>
struct is_callable : std::is_function<T> { };
diff --git a/test/parallel/test-crypto-cipheriv-decipheriv.js b/test/parallel/test-crypto-cipheriv-decipheriv.js
index c0073abcfd..e4c7fced58 100644
--- a/test/parallel/test-crypto-cipheriv-decipheriv.js
+++ b/test/parallel/test-crypto-cipheriv-decipheriv.js
@@ -101,8 +101,8 @@ function testCipher3(key, iv) {
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
- message: 'The "key" argument must be one of type string, Buffer, ' +
- 'TypedArray, or DataView. Received type object'
+ message: 'The "key" argument must be one of type Buffer, TypedArray, ' +
+ 'DataView, string, or KeyObject. Received type object'
});
common.expectsError(
@@ -138,8 +138,8 @@ function testCipher3(key, iv) {
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
- message: 'The "key" argument must be one of type string, Buffer, ' +
- 'TypedArray, or DataView. Received type object'
+ message: 'The "key" argument must be one of type Buffer, TypedArray, ' +
+ 'DataView, string, or KeyObject. Received type object'
});
common.expectsError(
diff --git a/test/parallel/test-crypto-hmac.js b/test/parallel/test-crypto-hmac.js
index f21db29d36..9e0e364a4f 100644
--- a/test/parallel/test-crypto-hmac.js
+++ b/test/parallel/test-crypto-hmac.js
@@ -36,20 +36,37 @@ common.expectsError(
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
- message: 'The "key" argument must be one of type string, TypedArray, or ' +
- 'DataView. Received type object'
+ message: 'The "key" argument must be one of type Buffer, TypedArray, ' +
+ 'DataView, string, or KeyObject. Received type object'
});
+function testHmac(algo, key, data, expected) {
+ // FIPS does not support MD5.
+ if (common.hasFipsCrypto && algo === 'md5')
+ return;
+
+ if (!Array.isArray(data))
+ data = [data];
+
+ // If the key is a Buffer, test Hmac with a key object as well.
+ const keyWrappers = [
+ (key) => key,
+ ...(typeof key === 'string' ? [] : [crypto.createSecretKey])
+ ];
+
+ for (const keyWrapper of keyWrappers) {
+ const hmac = crypto.createHmac(algo, keyWrapper(key));
+ for (const chunk of data)
+ hmac.update(chunk);
+ const actual = hmac.digest('hex');
+ assert.strictEqual(actual, expected);
+ }
+}
+
{
- // Test HMAC
- const actual = crypto.createHmac('sha1', 'Node')
- .update('some data')
- .update('to hmac')
- .digest('hex');
- const expected = '19fd6e1ba73d9ed2224dd5094a71babe85d9a892';
- assert.strictEqual(actual,
- expected,
- `Test HMAC: ${actual} must be ${expected}`);
+ // Test HMAC with multiple updates.
+ testHmac('sha1', 'Node', ['some data', 'to hmac'],
+ '19fd6e1ba73d9ed2224dd5094a71babe85d9a892');
}
// Test HMAC (Wikipedia Test Cases)
@@ -96,24 +113,11 @@ const wikipedia = [
},
];
-for (let i = 0, l = wikipedia.length; i < l; i++) {
- for (const hash in wikipedia[i].hmac) {
- // FIPS does not support MD5.
- if (common.hasFipsCrypto && hash === 'md5')
- continue;
- const expected = wikipedia[i].hmac[hash];
- const actual = crypto.createHmac(hash, wikipedia[i].key)
- .update(wikipedia[i].data)
- .digest('hex');
- assert.strictEqual(
- actual,
- expected,
- `Test HMAC-${hash} wikipedia case ${i + 1}: ${actual} must be ${expected}`
- );
- }
+for (const { key, data, hmac } of wikipedia) {
+ for (const hash in hmac)
+ testHmac(hash, key, data, hmac[hash]);
}
-
// Test HMAC-SHA-* (rfc 4231 Test Cases)
const rfc4231 = [
{
@@ -342,6 +346,10 @@ const rfc2202_md5 = [
hmac: '6f630fad67cda0ee1fb1f562db3aa53e'
}
];
+
+for (const { key, data, hmac } of rfc2202_md5)
+ testHmac('md5', key, data, hmac);
+
const rfc2202_sha1 = [
{
key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),
@@ -397,30 +405,8 @@ const rfc2202_sha1 = [
}
];
-if (!common.hasFipsCrypto) {
- for (let i = 0, l = rfc2202_md5.length; i < l; i++) {
- const actual = crypto.createHmac('md5', rfc2202_md5[i].key)
- .update(rfc2202_md5[i].data)
- .digest('hex');
- const expected = rfc2202_md5[i].hmac;
- assert.strictEqual(
- actual,
- expected,
- `Test HMAC-MD5 rfc 2202 case ${i + 1}: ${actual} must be ${expected}`
- );
- }
-}
-for (let i = 0, l = rfc2202_sha1.length; i < l; i++) {
- const actual = crypto.createHmac('sha1', rfc2202_sha1[i].key)
- .update(rfc2202_sha1[i].data)
- .digest('hex');
- const expected = rfc2202_sha1[i].hmac;
- assert.strictEqual(
- actual,
- expected,
- `Test HMAC-SHA1 rfc 2202 case ${i + 1}: ${actual} must be ${expected}`
- );
-}
+for (const { key, data, hmac } of rfc2202_sha1)
+ testHmac('sha1', key, data, hmac);
common.expectsError(
() => crypto.createHmac('sha256', 'w00t').digest('ucs2'),
diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js
new file mode 100644
index 0000000000..dddbd5f270
--- /dev/null
+++ b/test/parallel/test-crypto-key-objects.js
@@ -0,0 +1,107 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const {
+ createCipheriv,
+ createDecipheriv,
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey,
+ randomBytes,
+ publicEncrypt,
+ privateDecrypt
+} = require('crypto');
+
+const fixtures = require('../common/fixtures');
+
+const publicPem = fixtures.readSync('test_rsa_pubkey.pem', 'ascii');
+const privatePem = fixtures.readSync('test_rsa_privkey.pem', 'ascii');
+
+{
+ // Attempting to create an empty key should throw.
+ common.expectsError(() => {
+ createSecretKey(Buffer.alloc(0));
+ }, {
+ type: RangeError,
+ code: 'ERR_OUT_OF_RANGE',
+ message: 'The value of "key.byteLength" is out of range. ' +
+ 'It must be > 0. Received 0'
+ });
+}
+
+{
+ const keybuf = randomBytes(32);
+ const key = createSecretKey(keybuf);
+ assert.strictEqual(key.type, 'secret');
+ assert.strictEqual(key.symmetricKeySize, 32);
+ assert.strictEqual(key.asymmetricKeyType, undefined);
+
+ const exportedKey = key.export();
+ assert(keybuf.equals(exportedKey));
+
+ const plaintext = Buffer.from('Hello world', 'utf8');
+
+ const cipher = createCipheriv('aes-256-ecb', key, null);
+ const ciphertext = Buffer.concat([
+ cipher.update(plaintext), cipher.final()
+ ]);
+
+ const decipher = createDecipheriv('aes-256-ecb', key, null);
+ const deciphered = Buffer.concat([
+ decipher.update(ciphertext), decipher.final()
+ ]);
+
+ assert(plaintext.equals(deciphered));
+}
+
+{
+ const publicKey = createPublicKey(publicPem);
+ assert.strictEqual(publicKey.type, 'public');
+ assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
+ assert.strictEqual(publicKey.symmetricKeySize, undefined);
+
+ const privateKey = createPrivateKey(privatePem);
+ assert.strictEqual(privateKey.type, 'private');
+ assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
+ assert.strictEqual(privateKey.symmetricKeySize, undefined);
+
+ const publicDER = publicKey.export({
+ format: 'der',
+ type: 'pkcs1'
+ });
+
+ const privateDER = privateKey.export({
+ format: 'der',
+ type: 'pkcs1'
+ });
+
+ assert(Buffer.isBuffer(publicDER));
+ assert(Buffer.isBuffer(privateDER));
+
+ const plaintext = Buffer.from('Hello world', 'utf8');
+ const ciphertexts = [
+ publicEncrypt(publicKey, plaintext),
+ publicEncrypt({ key: publicKey }, plaintext),
+ // Test distinguishing PKCS#1 public and private keys based on the
+ // DER-encoded data only.
+ publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
+ publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext)
+ ];
+
+ const decryptionKeys = [
+ privateKey,
+ { format: 'pem', key: privatePem },
+ { format: 'der', type: 'pkcs1', key: privateDER }
+ ];
+
+ for (const ciphertext of ciphertexts) {
+ for (const key of decryptionKeys) {
+ const deciphered = privateDecrypt(key, ciphertext);
+ assert(plaintext.equals(deciphered));
+ }
+ }
+}
diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js
index 241e4aa73a..43319c3859 100644
--- a/test/parallel/test-crypto-keygen.js
+++ b/test/parallel/test-crypto-keygen.js
@@ -65,23 +65,6 @@ const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY');
const sec1Exp = getRegExpForPEM('EC PRIVATE KEY');
const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
-// 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.
@@ -113,14 +96,16 @@ function convertDERToPEM(label, der) {
}
{
+ const publicKeyEncoding = {
+ type: 'pkcs1',
+ format: 'der'
+ };
+
// Test async RSA key generation.
generateKeyPair('rsa', {
publicExponent: 0x10001,
modulusLength: 512,
- publicKeyEncoding: {
- type: 'pkcs1',
- format: 'der'
- },
+ publicKeyEncoding,
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem'
@@ -128,16 +113,14 @@ function convertDERToPEM(label, der) {
}, 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, 180);
+ assertApproximateSize(publicKeyDER, 74);
assert.strictEqual(typeof privateKey, 'string');
assert(pkcs1PrivExp.test(privateKey));
assertApproximateSize(privateKey, 512);
+ const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
testEncryptDecrypt(publicKey, privateKey);
testSignVerify(publicKey, privateKey);
}));
@@ -146,10 +129,7 @@ function convertDERToPEM(label, der) {
generateKeyPair('rsa', {
publicExponent: 0x10001,
modulusLength: 512,
- publicKeyEncoding: {
- type: 'pkcs1',
- format: 'der'
- },
+ publicKeyEncoding,
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
@@ -159,16 +139,14 @@ function convertDERToPEM(label, der) {
}, 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, 180);
+ assertApproximateSize(publicKeyDER, 74);
assert.strictEqual(typeof privateKey, 'string');
assert(pkcs1EncExp('AES-256-CBC').test(privateKey));
// Since the private key is encrypted, signing shouldn't work anymore.
+ const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
assert.throws(() => {
testSignVerify(publicKey, privateKey);
}, /bad decrypt|asn1 encoding routines/);
@@ -180,6 +158,11 @@ function convertDERToPEM(label, der) {
}
{
+ const privateKeyEncoding = {
+ type: 'pkcs8',
+ format: 'der'
+ };
+
// Test async DSA key generation.
generateKeyPair('dsa', {
modulusLength: 512,
@@ -189,10 +172,9 @@ function convertDERToPEM(label, der) {
format: 'pem'
},
privateKeyEncoding: {
- type: 'pkcs8',
- format: 'der',
cipher: 'aes-128-cbc',
- passphrase: 'secret'
+ passphrase: 'secret',
+ ...privateKeyEncoding
}
}, common.mustCall((err, publicKey, privateKeyDER) => {
assert.ifError(err);
@@ -201,19 +183,22 @@ function convertDERToPEM(label, der) {
assert(spkiExp.test(publicKey));
// The private key is DER-encoded.
assert(Buffer.isBuffer(privateKeyDER));
- const privateKey = convertDERToPEM('ENCRYPTED PRIVATE KEY', privateKeyDER);
assertApproximateSize(publicKey, 440);
- assertApproximateSize(privateKey, 512);
+ assertApproximateSize(privateKeyDER, 336);
// Since the private key is encrypted, signing shouldn't work anymore.
assert.throws(() => {
- testSignVerify(publicKey, privateKey);
+ testSignVerify(publicKey, {
+ key: privateKeyDER,
+ ...privateKeyEncoding
+ });
}, /bad decrypt|asn1 encoding routines/);
// Signing should work with the correct password.
testSignVerify(publicKey, {
- key: privateKey,
+ key: privateKeyDER,
+ ...privateKeyEncoding,
passphrase: 'secret'
});
}));
@@ -369,8 +354,52 @@ function convertDERToPEM(label, der) {
}
{
- // Missing / invalid publicKeyEncoding.
- for (const enc of [undefined, null, 0, 'a', true]) {
+ // If no publicKeyEncoding is specified, a key object should be returned.
+ generateKeyPair('rsa', {
+ modulusLength: 1024,
+ privateKeyEncoding: {
+ type: 'pkcs1',
+ format: 'pem'
+ }
+ }, common.mustCall((err, publicKey, privateKey) => {
+ assert.ifError(err);
+
+ assert.strictEqual(typeof publicKey, 'object');
+ assert.strictEqual(publicKey.type, 'public');
+ assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
+
+ // The private key should still be a string.
+ assert.strictEqual(typeof privateKey, 'string');
+
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ }));
+
+ // If no privateKeyEncoding is specified, a key object should be returned.
+ generateKeyPair('rsa', {
+ modulusLength: 1024,
+ publicKeyEncoding: {
+ type: 'pkcs1',
+ format: 'pem'
+ }
+ }, common.mustCall((err, publicKey, privateKey) => {
+ assert.ifError(err);
+
+ // The public key should still be a string.
+ assert.strictEqual(typeof publicKey, 'string');
+
+ assert.strictEqual(typeof privateKey, 'object');
+ assert.strictEqual(privateKey.type, 'private');
+ assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
+
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ }));
+}
+
+{
+ // Invalid publicKeyEncoding.
+ for (const enc of [0, 'a', true]) {
common.expectsError(() => generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: enc,
@@ -425,8 +454,8 @@ function convertDERToPEM(label, der) {
});
}
- // Missing / invalid privateKeyEncoding.
- for (const enc of [undefined, null, 0, 'a', true]) {
+ // Invalid privateKeyEncoding.
+ for (const enc of [0, 'a', true]) {
common.expectsError(() => generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
diff --git a/test/parallel/test-crypto-rsa-dsa.js b/test/parallel/test-crypto-rsa-dsa.js
index 744dc5657b..348fd15b74 100644
--- a/test/parallel/test-crypto-rsa-dsa.js
+++ b/test/parallel/test-crypto-rsa-dsa.js
@@ -100,7 +100,7 @@ const decryptError =
assert.throws(() => {
crypto.publicDecrypt({
key: rsaKeyPemEncrypted,
- passphrase: [].concat.apply([], Buffer.from('password'))
+ passphrase: Buffer.from('wrong')
}, encryptedBuffer);
}, decryptError);
}
diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js
index eaf555ff57..0499b3091c 100644
--- a/test/parallel/test-crypto-sign-verify.js
+++ b/test/parallel/test-crypto-sign-verify.js
@@ -352,7 +352,7 @@ common.expectsError(
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError [ERR_INVALID_ARG_TYPE]',
message: 'The "key" argument must be one of type string, Buffer, ' +
- `TypedArray, or DataView. Received type ${type}`
+ `TypedArray, DataView, or KeyObject. Received type ${type}`
};
assert.throws(() => sign.sign(input), errObj);
diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js
index 527b44f969..e358885620 100644
--- a/tools/doc/type-parser.js
+++ b/tools/doc/type-parser.js
@@ -50,6 +50,7 @@ const customTypesMap = {
'ECDH': 'crypto.html#crypto_class_ecdh',
'Hash': 'crypto.html#crypto_class_hash',
'Hmac': 'crypto.html#crypto_class_hmac',
+ 'KeyObject': 'crypto.html#crypto_class_keyobject',
'Sign': 'crypto.html#crypto_class_sign',
'Verify': 'crypto.html#crypto_class_verify',
'crypto.constants': 'crypto.html#crypto_crypto_constants_1',