crypto: add key object API

This commit makes multiple important changes:

1. A new key object API is introduced. The KeyObject class itself is
   not exposed to users, instead, several new APIs can be used to
   construct key objects: createSecretKey, createPrivateKey and
   createPublicKey. The new API also allows to convert between
   different key formats, and even though the API itself is not
   compatible to the WebCrypto standard in any way, it makes
   interoperability much simpler.

2. Key objects can be used instead of the raw key material in all
   relevant crypto APIs.

3. The handling of asymmetric keys has been unified and greatly
   improved. Node.js now fully supports both PEM-encoded and
   DER-encoded public and private keys.

4. Conversions between buffers and strings have been moved to native
   code for sensitive data such as symmetric keys due to security
   considerations such as zeroing temporary buffers.

5. For compatibility with older versions of the crypto API, this
   change allows to specify Buffers and strings as the "passphrase"
   option when reading or writing an encoded key. Note that this
   can result in unexpected behavior if the password contains a
   null byte.

PR-URL: https://github.com/nodejs/node/pull/24234
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Tobias Nießen 2018-09-20 19:53:44 +02:00
parent 5570df407a
commit 823d86c47c
21 changed files with 1996 additions and 651 deletions

View File

@ -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

View File

@ -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

View File

@ -59,6 +59,11 @@ const {
generateKeyPair,
generateKeyPairSync
} = require('internal/crypto/keygen');
const {
createSecretKey,
createPublicKey,
createPrivateKey
} = require('internal/crypto/keys');
const {
DiffieHellman,
DiffieHellmanGroup,
@ -149,6 +154,9 @@ module.exports = exports = {
createECDH,
createHash,
createHmac,
createPrivateKey,
createPublicKey,
createSecretKey,
createSign,
createVerify,
getCiphers,

View File

@ -12,6 +12,11 @@ const {
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const {
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey
} = require('internal/crypto/keys');
const {
getDefaultEncoding,
kHandle,
@ -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);

View File

@ -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] = {

View File

@ -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')
let publicFormat, publicType;
if (publicKeyEncoding == null) {
publicFormat = publicType = undefined;
} else if (typeof publicKeyEncoding === 'object') {
({
format: publicFormat,
type: publicType
} = parsePublicKeyEncoding(publicKeyEncoding, keyType,
'publicKeyEncoding'));
} else {
throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding', publicKeyEncoding);
const { format: strPublicFormat, type: strPublicType } = publicKeyEncoding;
let publicType;
if (strPublicType === 'pkcs1') {
if (keyType !== 'rsa') {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
strPublicType, 'can only be used for RSA keys');
}
publicType = PK_ENCODING_PKCS1;
} else if (strPublicType === 'spki') {
publicType = PK_ENCODING_SPKI;
} else {
throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.type', strPublicType);
}
let publicFormat;
if (strPublicFormat === 'der') {
publicFormat = PK_FORMAT_DER;
} else if (strPublicFormat === 'pem') {
publicFormat = PK_FORMAT_PEM;
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;

337
lib/internal/crypto/keys.js Normal file
View File

@ -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
};

View File

@ -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);
const rsaPadding = getPadding(options);
const pssSaltLength = getSaltLength(options);
var 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);

View File

@ -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);

View File

@ -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',

View File

@ -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) \

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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> { };

View File

@ -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(

View File

@ -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'),

View File

@ -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));
}
}
}

View File

@ -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: {

View File

@ -100,7 +100,7 @@ const decryptError =
assert.throws(() => {
crypto.publicDecrypt({
key: rsaKeyPemEncrypted,
passphrase: [].concat.apply([], Buffer.from('password'))
passphrase: Buffer.from('wrong')
}, encryptedBuffer);
}, decryptError);
}

View File

@ -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);

View File

@ -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',