crypto: add scrypt() and scryptSync() methods
Scrypt is a password-based key derivation function that is designed to be expensive both computationally and memory-wise in order to make brute-force attacks unrewarding. OpenSSL has had support for the scrypt algorithm since v1.1.0. Add a Node.js API modeled after `crypto.pbkdf2()` and `crypto.pbkdf2Sync()`. Changes: * Introduce helpers for copying buffers, collecting openssl errors, etc. * Add new infrastructure for offloading crypto to a worker thread. * Add a `AsyncWrap` JS class to simplify pbkdf2(), randomBytes() and scrypt(). Fixes: https://github.com/nodejs/node/issues/8417 PR-URL: https://github.com/nodejs/node/pull/20816 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
parent
58176e352c
commit
371103dae8
@ -1361,9 +1361,9 @@ password always creates the same key. The low iteration count and
|
|||||||
non-cryptographically secure hash algorithm allow passwords to be tested very
|
non-cryptographically secure hash algorithm allow passwords to be tested very
|
||||||
rapidly.
|
rapidly.
|
||||||
|
|
||||||
In line with OpenSSL's recommendation to use PBKDF2 instead of
|
In line with OpenSSL's recommendation to use a more modern algorithm instead of
|
||||||
[`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on
|
[`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on
|
||||||
their own using [`crypto.pbkdf2()`][] and to use [`crypto.createCipheriv()`][]
|
their own using [`crypto.scrypt()`][] and to use [`crypto.createCipheriv()`][]
|
||||||
to create the `Cipher` object. Users should not use ciphers with counter mode
|
to create the `Cipher` object. Users should not use ciphers with counter mode
|
||||||
(e.g. CTR, GCM, or CCM) in `crypto.createCipher()`. A warning is emitted when
|
(e.g. CTR, GCM, or CCM) in `crypto.createCipher()`. A warning is emitted when
|
||||||
they are used in order to avoid the risk of IV reuse that causes
|
they are used in order to avoid the risk of IV reuse that causes
|
||||||
@ -1463,9 +1463,9 @@ password always creates the same key. The low iteration count and
|
|||||||
non-cryptographically secure hash algorithm allow passwords to be tested very
|
non-cryptographically secure hash algorithm allow passwords to be tested very
|
||||||
rapidly.
|
rapidly.
|
||||||
|
|
||||||
In line with OpenSSL's recommendation to use PBKDF2 instead of
|
In line with OpenSSL's recommendation to use a more modern algorithm instead of
|
||||||
[`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on
|
[`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on
|
||||||
their own using [`crypto.pbkdf2()`][] and to use [`crypto.createDecipheriv()`][]
|
their own using [`crypto.scrypt()`][] and to use [`crypto.createDecipheriv()`][]
|
||||||
to create the `Decipher` object.
|
to create the `Decipher` object.
|
||||||
|
|
||||||
### crypto.createDecipheriv(algorithm, key, iv[, options])
|
### crypto.createDecipheriv(algorithm, key, iv[, options])
|
||||||
@ -1801,9 +1801,8 @@ The `iterations` argument must be a number set as high as possible. The
|
|||||||
higher the number of iterations, the more secure the derived key will be,
|
higher the number of iterations, the more secure the derived key will be,
|
||||||
but will take a longer amount of time to complete.
|
but will take a longer amount of time to complete.
|
||||||
|
|
||||||
The `salt` should also be as unique as possible. It is recommended that the
|
The `salt` should be as unique as possible. It is recommended that a salt is
|
||||||
salts are random and their lengths are at least 16 bytes. See
|
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
|
||||||
[NIST SP 800-132][] for details.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -1867,9 +1866,8 @@ The `iterations` argument must be a number set as high as possible. The
|
|||||||
higher the number of iterations, the more secure the derived key will be,
|
higher the number of iterations, the more secure the derived key will be,
|
||||||
but will take a longer amount of time to complete.
|
but will take a longer amount of time to complete.
|
||||||
|
|
||||||
The `salt` should also be as unique as possible. It is recommended that the
|
The `salt` should be as unique as possible. It is recommended that a salt is
|
||||||
salts are random and their lengths are at least 16 bytes. See
|
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
|
||||||
[NIST SP 800-132][] for details.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -2143,6 +2141,91 @@ threadpool request. To minimize threadpool task length variation, partition
|
|||||||
large `randomFill` requests when doing so as part of fulfilling a client
|
large `randomFill` requests when doing so as part of fulfilling a client
|
||||||
request.
|
request.
|
||||||
|
|
||||||
|
### crypto.scrypt(password, salt, keylen[, options], callback)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
- `password` {string|Buffer|TypedArray}
|
||||||
|
- `salt` {string|Buffer|TypedArray}
|
||||||
|
- `keylen` {number}
|
||||||
|
- `options` {Object}
|
||||||
|
- `N` {number} CPU/memory cost parameter. Must be a power of two greater
|
||||||
|
than one. **Default:** `16384`.
|
||||||
|
- `r` {number} Block size parameter. **Default:** `8`.
|
||||||
|
- `p` {number} Parallelization parameter. **Default:** `1`.
|
||||||
|
- `maxmem` {number} Memory upper bound. It is an error when (approximately)
|
||||||
|
`128*N*r > maxmem` **Default:** `32 * 1024 * 1024`.
|
||||||
|
- `callback` {Function}
|
||||||
|
- `err` {Error}
|
||||||
|
- `derivedKey` {Buffer}
|
||||||
|
|
||||||
|
Provides an asynchronous [scrypt][] implementation. Scrypt is a password-based
|
||||||
|
key derivation function that is designed to be expensive computationally and
|
||||||
|
memory-wise in order to make brute-force attacks unrewarding.
|
||||||
|
|
||||||
|
The `salt` should be as unique as possible. It is recommended that a salt is
|
||||||
|
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
|
||||||
|
|
||||||
|
The `callback` function is called with two arguments: `err` and `derivedKey`.
|
||||||
|
`err` is an exception object when key derivation fails, otherwise `err` is
|
||||||
|
`null`. `derivedKey` is passed to the callback as a [`Buffer`][].
|
||||||
|
|
||||||
|
An exception is thrown when any of the input arguments specify invalid values
|
||||||
|
or types.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const crypto = require('crypto');
|
||||||
|
// Using the factory defaults.
|
||||||
|
crypto.scrypt('secret', 'salt', 64, (err, derivedKey) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log(derivedKey.toString('hex')); // '3745e48...08d59ae'
|
||||||
|
});
|
||||||
|
// Using a custom N parameter. Must be a power of two.
|
||||||
|
crypto.scrypt('secret', 'salt', 64, { N: 1024 }, (err, derivedKey) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log(derivedKey.toString('hex')); // '3745e48...aa39b34'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### crypto.scryptSync(password, salt, keylen[, options])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
- `password` {string|Buffer|TypedArray}
|
||||||
|
- `salt` {string|Buffer|TypedArray}
|
||||||
|
- `keylen` {number}
|
||||||
|
- `options` {Object}
|
||||||
|
- `N` {number} CPU/memory cost parameter. Must be a power of two greater
|
||||||
|
than one. **Default:** `16384`.
|
||||||
|
- `r` {number} Block size parameter. **Default:** `8`.
|
||||||
|
- `p` {number} Parallelization parameter. **Default:** `1`.
|
||||||
|
- `maxmem` {number} Memory upper bound. It is an error when (approximately)
|
||||||
|
`128*N*r > maxmem` **Default:** `32 * 1024 * 1024`.
|
||||||
|
- Returns: {Buffer}
|
||||||
|
|
||||||
|
Provides a synchronous [scrypt][] implementation. Scrypt is a password-based
|
||||||
|
key derivation function that is designed to be expensive computationally and
|
||||||
|
memory-wise in order to make brute-force attacks unrewarding.
|
||||||
|
|
||||||
|
The `salt` should be as unique as possible. It is recommended that a salt is
|
||||||
|
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
|
||||||
|
|
||||||
|
An exception is thrown when key derivation fails, otherwise the derived key is
|
||||||
|
returned as a [`Buffer`][].
|
||||||
|
|
||||||
|
An exception is thrown when any of the input arguments specify invalid values
|
||||||
|
or types.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const crypto = require('crypto');
|
||||||
|
// Using the factory defaults.
|
||||||
|
const key1 = crypto.scryptSync('secret', 'salt', 64);
|
||||||
|
console.log(key1.toString('hex')); // '3745e48...08d59ae'
|
||||||
|
// Using a custom N parameter. Must be a power of two.
|
||||||
|
const key2 = crypto.scryptSync('secret', 'salt', 64, { N: 1024 });
|
||||||
|
console.log(key2.toString('hex')); // '3745e48...aa39b34'
|
||||||
|
```
|
||||||
|
|
||||||
### crypto.setEngine(engine[, flags])
|
### crypto.setEngine(engine[, flags])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.11.11
|
added: v0.11.11
|
||||||
@ -2650,9 +2733,9 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
|
|||||||
[`crypto.createVerify()`]: #crypto_crypto_createverify_algorithm_options
|
[`crypto.createVerify()`]: #crypto_crypto_createverify_algorithm_options
|
||||||
[`crypto.getCurves()`]: #crypto_crypto_getcurves
|
[`crypto.getCurves()`]: #crypto_crypto_getcurves
|
||||||
[`crypto.getHashes()`]: #crypto_crypto_gethashes
|
[`crypto.getHashes()`]: #crypto_crypto_gethashes
|
||||||
[`crypto.pbkdf2()`]: #crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback
|
|
||||||
[`crypto.randomBytes()`]: #crypto_crypto_randombytes_size_callback
|
[`crypto.randomBytes()`]: #crypto_crypto_randombytes_size_callback
|
||||||
[`crypto.randomFill()`]: #crypto_crypto_randomfill_buffer_offset_size_callback
|
[`crypto.randomFill()`]: #crypto_crypto_randomfill_buffer_offset_size_callback
|
||||||
|
[`crypto.scrypt()`]: #crypto_crypto_scrypt_password_salt_keylen_options_callback
|
||||||
[`decipher.final()`]: #crypto_decipher_final_outputencoding
|
[`decipher.final()`]: #crypto_decipher_final_outputencoding
|
||||||
[`decipher.update()`]: #crypto_decipher_update_data_inputencoding_outputencoding
|
[`decipher.update()`]: #crypto_decipher_update_data_inputencoding_outputencoding
|
||||||
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_publickey_encoding
|
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_publickey_encoding
|
||||||
@ -2686,5 +2769,6 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
|
|||||||
[RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt
|
[RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt
|
||||||
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
|
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
|
||||||
[initialization vector]: https://en.wikipedia.org/wiki/Initialization_vector
|
[initialization vector]: https://en.wikipedia.org/wiki/Initialization_vector
|
||||||
|
[scrypt]: https://en.wikipedia.org/wiki/Scrypt
|
||||||
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
|
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
|
||||||
[stream]: stream.html
|
[stream]: stream.html
|
||||||
|
@ -739,6 +739,18 @@ An invalid [crypto digest algorithm][] was specified.
|
|||||||
A crypto method was used on an object that was in an invalid state. For
|
A crypto method was used on an object that was in an invalid state. For
|
||||||
instance, calling [`cipher.getAuthTag()`][] before calling `cipher.final()`.
|
instance, calling [`cipher.getAuthTag()`][] before calling `cipher.final()`.
|
||||||
|
|
||||||
|
<a id="ERR_CRYPTO_SCRYPT_INVALID_PARAMETER"></a>
|
||||||
|
### ERR_CRYPTO_SCRYPT_INVALID_PARAMETER
|
||||||
|
|
||||||
|
One or more [`crypto.scrypt()`][] or [`crypto.scryptSync()`][] parameters are
|
||||||
|
outside their legal range.
|
||||||
|
|
||||||
|
<a id="ERR_CRYPTO_SCRYPT_NOT_SUPPORTED"></a>
|
||||||
|
### ERR_CRYPTO_SCRYPT_NOT_SUPPORTED
|
||||||
|
|
||||||
|
Node.js was compiled without `scrypt` support. Not possible with the official
|
||||||
|
release binaries but can happen with custom builds, including distro builds.
|
||||||
|
|
||||||
<a id="ERR_CRYPTO_SIGN_KEY_REQUIRED"></a>
|
<a id="ERR_CRYPTO_SIGN_KEY_REQUIRED"></a>
|
||||||
### ERR_CRYPTO_SIGN_KEY_REQUIRED
|
### ERR_CRYPTO_SIGN_KEY_REQUIRED
|
||||||
|
|
||||||
@ -1749,6 +1761,8 @@ Creation of a [`zlib`][] object failed due to incorrect configuration.
|
|||||||
[`child_process`]: child_process.html
|
[`child_process`]: child_process.html
|
||||||
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
|
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
|
||||||
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror
|
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror
|
||||||
|
[`crypto.scrypt()`]: crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback
|
||||||
|
[`crypto.scryptSync()`]: crypto.html#crypto_crypto_scryptSync_password_salt_keylen_options
|
||||||
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b
|
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b
|
||||||
[`dgram.createSocket()`]: dgram.html#dgram_dgram_createsocket_options_callback
|
[`dgram.createSocket()`]: dgram.html#dgram_dgram_createsocket_options_callback
|
||||||
[`ERR_INVALID_ARG_TYPE`]: #ERR_INVALID_ARG_TYPE
|
[`ERR_INVALID_ARG_TYPE`]: #ERR_INVALID_ARG_TYPE
|
||||||
|
@ -52,6 +52,10 @@ const {
|
|||||||
pbkdf2,
|
pbkdf2,
|
||||||
pbkdf2Sync
|
pbkdf2Sync
|
||||||
} = require('internal/crypto/pbkdf2');
|
} = require('internal/crypto/pbkdf2');
|
||||||
|
const {
|
||||||
|
scrypt,
|
||||||
|
scryptSync
|
||||||
|
} = require('internal/crypto/scrypt');
|
||||||
const {
|
const {
|
||||||
DiffieHellman,
|
DiffieHellman,
|
||||||
DiffieHellmanGroup,
|
DiffieHellmanGroup,
|
||||||
@ -163,6 +167,8 @@ module.exports = exports = {
|
|||||||
randomFill,
|
randomFill,
|
||||||
randomFillSync,
|
randomFillSync,
|
||||||
rng: randomBytes,
|
rng: randomBytes,
|
||||||
|
scrypt,
|
||||||
|
scryptSync,
|
||||||
setEngine,
|
setEngine,
|
||||||
timingSafeEqual,
|
timingSafeEqual,
|
||||||
getFips: !fipsMode ? getFipsDisabled :
|
getFips: !fipsMode ? getFipsDisabled :
|
||||||
|
97
lib/internal/crypto/scrypt.js
Normal file
97
lib/internal/crypto/scrypt.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { AsyncWrap, Providers } = process.binding('async_wrap');
|
||||||
|
const { Buffer } = require('buffer');
|
||||||
|
const { scrypt: _scrypt } = process.binding('crypto');
|
||||||
|
const {
|
||||||
|
ERR_CRYPTO_SCRYPT_INVALID_PARAMETER,
|
||||||
|
ERR_CRYPTO_SCRYPT_NOT_SUPPORTED,
|
||||||
|
ERR_INVALID_CALLBACK,
|
||||||
|
} = require('internal/errors').codes;
|
||||||
|
const {
|
||||||
|
checkIsArrayBufferView,
|
||||||
|
checkIsUint,
|
||||||
|
getDefaultEncoding,
|
||||||
|
} = require('internal/crypto/util');
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
N: 16384,
|
||||||
|
r: 8,
|
||||||
|
p: 1,
|
||||||
|
maxmem: 32 << 20, // 32 MB, matches SCRYPT_MAX_MEM.
|
||||||
|
};
|
||||||
|
|
||||||
|
function scrypt(password, salt, keylen, options, callback = defaults) {
|
||||||
|
if (callback === defaults) {
|
||||||
|
callback = options;
|
||||||
|
options = defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = check(password, salt, keylen, options);
|
||||||
|
const { N, r, p, maxmem } = options;
|
||||||
|
({ password, salt, keylen } = options);
|
||||||
|
|
||||||
|
if (typeof callback !== 'function')
|
||||||
|
throw new ERR_INVALID_CALLBACK();
|
||||||
|
|
||||||
|
const encoding = getDefaultEncoding();
|
||||||
|
const keybuf = Buffer.alloc(keylen);
|
||||||
|
|
||||||
|
const wrap = new AsyncWrap(Providers.SCRYPTREQUEST);
|
||||||
|
wrap.ondone = (ex) => { // Retains keybuf while request is in flight.
|
||||||
|
if (ex) return callback.call(wrap, ex);
|
||||||
|
if (encoding === 'buffer') return callback.call(wrap, null, keybuf);
|
||||||
|
callback.call(wrap, null, keybuf.toString(encoding));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleError(keybuf, password, salt, N, r, p, maxmem, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scryptSync(password, salt, keylen, options = defaults) {
|
||||||
|
options = check(password, salt, keylen, options);
|
||||||
|
const { N, r, p, maxmem } = options;
|
||||||
|
({ password, salt, keylen } = options);
|
||||||
|
const keybuf = Buffer.alloc(keylen);
|
||||||
|
handleError(keybuf, password, salt, N, r, p, maxmem);
|
||||||
|
const encoding = getDefaultEncoding();
|
||||||
|
if (encoding === 'buffer') return keybuf;
|
||||||
|
return keybuf.toString(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(keybuf, password, salt, N, r, p, maxmem, wrap) {
|
||||||
|
const ex = _scrypt(keybuf, password, salt, N, r, p, maxmem, wrap);
|
||||||
|
|
||||||
|
if (ex === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ex === null)
|
||||||
|
throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); // Bad N, r, p, or maxmem.
|
||||||
|
|
||||||
|
throw ex; // Scrypt operation failed, exception object contains details.
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(password, salt, keylen, options, callback) {
|
||||||
|
if (_scrypt === undefined)
|
||||||
|
throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED();
|
||||||
|
|
||||||
|
password = checkIsArrayBufferView('password', password);
|
||||||
|
salt = checkIsArrayBufferView('salt', salt);
|
||||||
|
keylen = checkIsUint('keylen', keylen);
|
||||||
|
|
||||||
|
let { N, r, p, maxmem } = defaults;
|
||||||
|
if (options && options !== defaults) {
|
||||||
|
if (options.hasOwnProperty('N')) N = checkIsUint('N', options.N);
|
||||||
|
if (options.hasOwnProperty('r')) r = checkIsUint('r', options.r);
|
||||||
|
if (options.hasOwnProperty('p')) p = checkIsUint('p', options.p);
|
||||||
|
if (options.hasOwnProperty('maxmem'))
|
||||||
|
maxmem = checkIsUint('maxmem', options.maxmem);
|
||||||
|
if (N === 0) N = defaults.N;
|
||||||
|
if (r === 0) r = defaults.r;
|
||||||
|
if (p === 0) p = defaults.p;
|
||||||
|
if (maxmem === 0) maxmem = defaults.maxmem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { password, salt, keylen, N, r, p, maxmem };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { scrypt, scryptSync };
|
@ -500,7 +500,8 @@ E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called', Error);
|
|||||||
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error);
|
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error);
|
||||||
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
|
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
|
||||||
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
|
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
|
||||||
|
E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error);
|
||||||
|
E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error);
|
||||||
// Switch to TypeError. The current implementation does not seem right.
|
// Switch to TypeError. The current implementation does not seem right.
|
||||||
E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign', Error);
|
E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign', Error);
|
||||||
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
|
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
|
||||||
|
1
node.gyp
1
node.gyp
@ -97,6 +97,7 @@
|
|||||||
'lib/internal/crypto/hash.js',
|
'lib/internal/crypto/hash.js',
|
||||||
'lib/internal/crypto/pbkdf2.js',
|
'lib/internal/crypto/pbkdf2.js',
|
||||||
'lib/internal/crypto/random.js',
|
'lib/internal/crypto/random.js',
|
||||||
|
'lib/internal/crypto/scrypt.js',
|
||||||
'lib/internal/crypto/sig.js',
|
'lib/internal/crypto/sig.js',
|
||||||
'lib/internal/crypto/util.js',
|
'lib/internal/crypto/util.js',
|
||||||
'lib/internal/constants.js',
|
'lib/internal/constants.js',
|
||||||
|
@ -45,6 +45,7 @@ using v8::PromiseHookType;
|
|||||||
using v8::PropertyCallbackInfo;
|
using v8::PropertyCallbackInfo;
|
||||||
using v8::RetainedObjectInfo;
|
using v8::RetainedObjectInfo;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
|
using v8::Uint32;
|
||||||
using v8::Undefined;
|
using v8::Undefined;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
@ -133,6 +134,23 @@ RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) {
|
|||||||
// end RetainedAsyncInfo
|
// end RetainedAsyncInfo
|
||||||
|
|
||||||
|
|
||||||
|
struct AsyncWrapObject : public AsyncWrap {
|
||||||
|
static inline void New(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
CHECK(args.IsConstructCall());
|
||||||
|
CHECK(env->async_wrap_constructor_template()->HasInstance(args.This()));
|
||||||
|
CHECK(args[0]->IsUint32());
|
||||||
|
auto type = static_cast<ProviderType>(args[0].As<Uint32>()->Value());
|
||||||
|
new AsyncWrapObject(env, args.This(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline AsyncWrapObject(Environment* env, Local<Object> object,
|
||||||
|
ProviderType type) : AsyncWrap(env, object, type) {}
|
||||||
|
|
||||||
|
inline size_t self_size() const override { return sizeof(*this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
static void DestroyAsyncIdsCallback(Environment* env, void* data) {
|
static void DestroyAsyncIdsCallback(Environment* env, void* data) {
|
||||||
Local<Function> fn = env->async_hooks_destroy_function();
|
Local<Function> fn = env->async_hooks_destroy_function();
|
||||||
|
|
||||||
@ -569,6 +587,19 @@ void AsyncWrap::Initialize(Local<Object> target,
|
|||||||
env->set_async_hooks_destroy_function(Local<Function>());
|
env->set_async_hooks_destroy_function(Local<Function>());
|
||||||
env->set_async_hooks_promise_resolve_function(Local<Function>());
|
env->set_async_hooks_promise_resolve_function(Local<Function>());
|
||||||
env->set_async_hooks_binding(target);
|
env->set_async_hooks_binding(target);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto class_name = FIXED_ONE_BYTE_STRING(env->isolate(), "AsyncWrap");
|
||||||
|
auto function_template = env->NewFunctionTemplate(AsyncWrapObject::New);
|
||||||
|
function_template->SetClassName(class_name);
|
||||||
|
AsyncWrap::AddWrapMethods(env, function_template);
|
||||||
|
auto instance_template = function_template->InstanceTemplate();
|
||||||
|
instance_template->SetInternalFieldCount(1);
|
||||||
|
auto function =
|
||||||
|
function_template->GetFunction(env->context()).ToLocalChecked();
|
||||||
|
target->Set(env->context(), class_name, function).FromJust();
|
||||||
|
env->set_async_wrap_constructor_template(function_template);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ namespace node {
|
|||||||
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
|
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
|
||||||
V(PBKDF2REQUEST) \
|
V(PBKDF2REQUEST) \
|
||||||
V(RANDOMBYTESREQUEST) \
|
V(RANDOMBYTESREQUEST) \
|
||||||
|
V(SCRYPTREQUEST) \
|
||||||
V(TLSWRAP)
|
V(TLSWRAP)
|
||||||
#else
|
#else
|
||||||
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
|
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
|
||||||
|
@ -319,6 +319,7 @@ struct PackageConfig {
|
|||||||
V(async_hooks_destroy_function, v8::Function) \
|
V(async_hooks_destroy_function, v8::Function) \
|
||||||
V(async_hooks_init_function, v8::Function) \
|
V(async_hooks_init_function, v8::Function) \
|
||||||
V(async_hooks_promise_resolve_function, v8::Function) \
|
V(async_hooks_promise_resolve_function, v8::Function) \
|
||||||
|
V(async_wrap_constructor_template, v8::FunctionTemplate) \
|
||||||
V(buffer_prototype_object, v8::Object) \
|
V(buffer_prototype_object, v8::Object) \
|
||||||
V(context, v8::Context) \
|
V(context, v8::Context) \
|
||||||
V(domain_callback, v8::Function) \
|
V(domain_callback, v8::Function) \
|
||||||
|
@ -78,6 +78,7 @@ using v8::Isolate;
|
|||||||
using v8::Local;
|
using v8::Local;
|
||||||
using v8::Maybe;
|
using v8::Maybe;
|
||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
|
using v8::NewStringType;
|
||||||
using v8::Null;
|
using v8::Null;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
using v8::ObjectTemplate;
|
using v8::ObjectTemplate;
|
||||||
@ -204,57 +205,75 @@ static int NoPasswordCallback(char* buf, int size, int rwflag, void* u) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct CryptoErrorVector : public std::vector<std::string> {
|
||||||
|
inline void Capture() {
|
||||||
|
clear();
|
||||||
|
while (auto err = ERR_get_error()) {
|
||||||
|
char buf[256];
|
||||||
|
ERR_error_string_n(err, buf, sizeof(buf));
|
||||||
|
push_back(buf);
|
||||||
|
}
|
||||||
|
std::reverse(begin(), end());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Local<Value> ToException(
|
||||||
|
Environment* env,
|
||||||
|
Local<String> exception_string = Local<String>()) const {
|
||||||
|
if (exception_string.IsEmpty()) {
|
||||||
|
CryptoErrorVector copy(*this);
|
||||||
|
if (copy.empty()) copy.push_back("no error"); // But possibly a bug...
|
||||||
|
// Use last element as the error message, everything else goes
|
||||||
|
// into the .opensslErrorStack property on the exception object.
|
||||||
|
auto exception_string =
|
||||||
|
String::NewFromUtf8(env->isolate(), copy.back().data(),
|
||||||
|
NewStringType::kNormal, copy.back().size())
|
||||||
|
.ToLocalChecked();
|
||||||
|
copy.pop_back();
|
||||||
|
return copy.ToException(env, exception_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Value> exception_v = Exception::Error(exception_string);
|
||||||
|
CHECK(!exception_v.IsEmpty());
|
||||||
|
|
||||||
|
if (!empty()) {
|
||||||
|
Local<Array> array = Array::New(env->isolate(), size());
|
||||||
|
CHECK(!array.IsEmpty());
|
||||||
|
|
||||||
|
for (const std::string& string : *this) {
|
||||||
|
const size_t index = &string - &front();
|
||||||
|
Local<String> value =
|
||||||
|
String::NewFromUtf8(env->isolate(), string.data(),
|
||||||
|
NewStringType::kNormal, string.size())
|
||||||
|
.ToLocalChecked();
|
||||||
|
array->Set(env->context(), index, value).FromJust();
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(exception_v->IsObject());
|
||||||
|
Local<Object> exception = exception_v.As<Object>();
|
||||||
|
exception->Set(env->context(),
|
||||||
|
env->openssl_error_stack(), array).FromJust();
|
||||||
|
}
|
||||||
|
|
||||||
|
return exception_v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
void ThrowCryptoError(Environment* env,
|
void ThrowCryptoError(Environment* env,
|
||||||
unsigned long err, // NOLINT(runtime/int)
|
unsigned long err, // NOLINT(runtime/int)
|
||||||
const char* default_message = nullptr) {
|
const char* message = nullptr) {
|
||||||
|
char message_buffer[128] = {0};
|
||||||
|
if (err != 0 || message == nullptr) {
|
||||||
|
ERR_error_string_n(err, message_buffer, sizeof(message_buffer));
|
||||||
|
message = message_buffer;
|
||||||
|
}
|
||||||
HandleScope scope(env->isolate());
|
HandleScope scope(env->isolate());
|
||||||
Local<String> message;
|
auto exception_string =
|
||||||
|
String::NewFromUtf8(env->isolate(), message, NewStringType::kNormal)
|
||||||
if (err != 0 || default_message == nullptr) {
|
|
||||||
char errmsg[128] = { 0 };
|
|
||||||
ERR_error_string_n(err, errmsg, sizeof(errmsg));
|
|
||||||
message = String::NewFromUtf8(env->isolate(), errmsg,
|
|
||||||
v8::NewStringType::kNormal)
|
|
||||||
.ToLocalChecked();
|
.ToLocalChecked();
|
||||||
} else {
|
CryptoErrorVector errors;
|
||||||
message = String::NewFromUtf8(env->isolate(), default_message,
|
errors.Capture();
|
||||||
v8::NewStringType::kNormal)
|
auto exception = errors.ToException(env, exception_string);
|
||||||
.ToLocalChecked();
|
|
||||||
}
|
|
||||||
|
|
||||||
Local<Value> exception_v = Exception::Error(message);
|
|
||||||
CHECK(!exception_v.IsEmpty());
|
|
||||||
Local<Object> exception = exception_v.As<Object>();
|
|
||||||
|
|
||||||
std::vector<Local<String>> errors;
|
|
||||||
for (;;) {
|
|
||||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
||||||
if (err == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
char tmp_str[256];
|
|
||||||
ERR_error_string_n(err, tmp_str, sizeof(tmp_str));
|
|
||||||
errors.push_back(String::NewFromUtf8(env->isolate(), tmp_str,
|
|
||||||
v8::NewStringType::kNormal)
|
|
||||||
.ToLocalChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ERR_get_error returns errors in order of most specific to least
|
|
||||||
// specific. We wish to have the reverse ordering:
|
|
||||||
// opensslErrorStack: [
|
|
||||||
// 'error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib',
|
|
||||||
// 'error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 err'
|
|
||||||
// ]
|
|
||||||
if (!errors.empty()) {
|
|
||||||
std::reverse(errors.begin(), errors.end());
|
|
||||||
Local<Array> errors_array = Array::New(env->isolate(), errors.size());
|
|
||||||
for (size_t i = 0; i < errors.size(); i++) {
|
|
||||||
errors_array->Set(env->context(), i, errors[i]).FromJust();
|
|
||||||
}
|
|
||||||
exception->Set(env->context(), env->openssl_error_stack(), errors_array)
|
|
||||||
.FromJust();
|
|
||||||
}
|
|
||||||
|
|
||||||
env->isolate()->ThrowException(exception);
|
env->isolate()->ThrowException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4529,6 +4548,43 @@ bool ECDH::IsKeyPairValid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct CryptoJob : public ThreadPoolWork {
|
||||||
|
Environment* const env;
|
||||||
|
std::unique_ptr<AsyncWrap> async_wrap;
|
||||||
|
inline explicit CryptoJob(Environment* env) : ThreadPoolWork(env), env(env) {}
|
||||||
|
inline void AfterThreadPoolWork(int status) final;
|
||||||
|
virtual void AfterThreadPoolWork() = 0;
|
||||||
|
static inline void Run(std::unique_ptr<CryptoJob> job, Local<Value> wrap);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void CryptoJob::AfterThreadPoolWork(int status) {
|
||||||
|
CHECK(status == 0 || status == UV_ECANCELED);
|
||||||
|
std::unique_ptr<CryptoJob> job(this);
|
||||||
|
if (status == UV_ECANCELED) return;
|
||||||
|
HandleScope handle_scope(env->isolate());
|
||||||
|
Context::Scope context_scope(env->context());
|
||||||
|
CHECK_EQ(false, async_wrap->persistent().IsWeak());
|
||||||
|
AfterThreadPoolWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CryptoJob::Run(std::unique_ptr<CryptoJob> job, Local<Value> wrap) {
|
||||||
|
CHECK(wrap->IsObject());
|
||||||
|
CHECK_EQ(nullptr, job->async_wrap);
|
||||||
|
job->async_wrap.reset(Unwrap<AsyncWrap>(wrap.As<Object>()));
|
||||||
|
CHECK_EQ(false, job->async_wrap->persistent().IsWeak());
|
||||||
|
job->ScheduleWork();
|
||||||
|
job.release(); // Run free, little job!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline void CopyBuffer(Local<Value> buf, std::vector<char>* vec) {
|
||||||
|
vec->clear();
|
||||||
|
if (auto p = Buffer::Data(buf)) vec->assign(p, p + Buffer::Length(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PBKDF2Request : public AsyncWrap, public ThreadPoolWork {
|
class PBKDF2Request : public AsyncWrap, public ThreadPoolWork {
|
||||||
public:
|
public:
|
||||||
PBKDF2Request(Environment* env,
|
PBKDF2Request(Environment* env,
|
||||||
@ -4870,6 +4926,98 @@ void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef OPENSSL_NO_SCRYPT
|
||||||
|
struct ScryptJob : public CryptoJob {
|
||||||
|
unsigned char* keybuf_data;
|
||||||
|
size_t keybuf_size;
|
||||||
|
std::vector<char> pass;
|
||||||
|
std::vector<char> salt;
|
||||||
|
uint32_t N;
|
||||||
|
uint32_t r;
|
||||||
|
uint32_t p;
|
||||||
|
uint32_t maxmem;
|
||||||
|
CryptoErrorVector errors;
|
||||||
|
|
||||||
|
inline explicit ScryptJob(Environment* env) : CryptoJob(env) {}
|
||||||
|
|
||||||
|
inline ~ScryptJob() override {
|
||||||
|
Cleanse();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Validate() {
|
||||||
|
if (1 == EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem,
|
||||||
|
nullptr, 0)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Note: EVP_PBE_scrypt() does not always put errors on the error stack.
|
||||||
|
errors.Capture();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DoThreadPoolWork() override {
|
||||||
|
auto salt_data = reinterpret_cast<const unsigned char*>(salt.data());
|
||||||
|
if (1 != EVP_PBE_scrypt(pass.data(), pass.size(), salt_data, salt.size(),
|
||||||
|
N, r, p, maxmem, keybuf_data, keybuf_size)) {
|
||||||
|
errors.Capture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AfterThreadPoolWork() override {
|
||||||
|
Local<Value> arg = ToResult();
|
||||||
|
async_wrap->MakeCallback(env->ondone_string(), 1, &arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Local<Value> ToResult() const {
|
||||||
|
if (errors.empty()) return Undefined(env->isolate());
|
||||||
|
return errors.ToException(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Cleanse() {
|
||||||
|
OPENSSL_cleanse(pass.data(), pass.size());
|
||||||
|
OPENSSL_cleanse(salt.data(), salt.size());
|
||||||
|
pass.clear();
|
||||||
|
salt.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void Scrypt(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
CHECK(args[0]->IsArrayBufferView()); // keybuf; wrap object retains ref.
|
||||||
|
CHECK(args[1]->IsArrayBufferView()); // pass
|
||||||
|
CHECK(args[2]->IsArrayBufferView()); // salt
|
||||||
|
CHECK(args[3]->IsUint32()); // N
|
||||||
|
CHECK(args[4]->IsUint32()); // r
|
||||||
|
CHECK(args[5]->IsUint32()); // p
|
||||||
|
CHECK(args[6]->IsUint32()); // maxmem
|
||||||
|
CHECK(args[7]->IsObject() || args[7]->IsUndefined()); // wrap object
|
||||||
|
std::unique_ptr<ScryptJob> job(new ScryptJob(env));
|
||||||
|
job->keybuf_data = reinterpret_cast<unsigned char*>(Buffer::Data(args[0]));
|
||||||
|
job->keybuf_size = Buffer::Length(args[0]);
|
||||||
|
CopyBuffer(args[1], &job->pass);
|
||||||
|
CopyBuffer(args[2], &job->salt);
|
||||||
|
job->N = args[3].As<Uint32>()->Value();
|
||||||
|
job->r = args[4].As<Uint32>()->Value();
|
||||||
|
job->p = args[5].As<Uint32>()->Value();
|
||||||
|
job->maxmem = args[6].As<Uint32>()->Value();
|
||||||
|
if (!job->Validate()) {
|
||||||
|
// EVP_PBE_scrypt() does not always put errors on the error stack
|
||||||
|
// and therefore ToResult() may or may not return an exception
|
||||||
|
// object. Return a sentinel value to inform JS land it should
|
||||||
|
// throw an ERR_CRYPTO_SCRYPT_PARAMETER_ERROR on our behalf.
|
||||||
|
auto result = job->ToResult();
|
||||||
|
if (result->IsUndefined()) result = Null(args.GetIsolate());
|
||||||
|
return args.GetReturnValue().Set(result);
|
||||||
|
}
|
||||||
|
if (args[7]->IsObject()) return ScryptJob::Run(std::move(job), args[7]);
|
||||||
|
env->PrintSyncTrace();
|
||||||
|
job->DoThreadPoolWork();
|
||||||
|
args.GetReturnValue().Set(job->ToResult());
|
||||||
|
}
|
||||||
|
#endif // OPENSSL_NO_SCRYPT
|
||||||
|
|
||||||
|
|
||||||
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
@ -5293,6 +5441,9 @@ void Initialize(Local<Object> target,
|
|||||||
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
|
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
|
||||||
EVP_PKEY_verify_recover_init,
|
EVP_PKEY_verify_recover_init,
|
||||||
EVP_PKEY_verify_recover>);
|
EVP_PKEY_verify_recover>);
|
||||||
|
#ifndef OPENSSL_NO_SCRYPT
|
||||||
|
env->SetMethod(target, "scrypt", Scrypt);
|
||||||
|
#endif // OPENSSL_NO_SCRYPT
|
||||||
|
|
||||||
Local<FunctionTemplate> pb = FunctionTemplate::New(env->isolate());
|
Local<FunctionTemplate> pb = FunctionTemplate::New(env->isolate());
|
||||||
pb->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "PBKDF2"));
|
pb->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "PBKDF2"));
|
||||||
|
@ -523,6 +523,8 @@ class InternalCallbackScope {
|
|||||||
class ThreadPoolWork {
|
class ThreadPoolWork {
|
||||||
public:
|
public:
|
||||||
explicit inline ThreadPoolWork(Environment* env) : env_(env) {}
|
explicit inline ThreadPoolWork(Environment* env) : env_(env) {}
|
||||||
|
inline virtual ~ThreadPoolWork() = default;
|
||||||
|
|
||||||
inline void ScheduleWork();
|
inline void ScheduleWork();
|
||||||
inline int CancelWork();
|
inline int CancelWork();
|
||||||
|
|
||||||
|
165
test/parallel/test-crypto-scrypt.js
Normal file
165
test/parallel/test-crypto-scrypt.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
if (typeof process.binding('crypto').scrypt !== 'function')
|
||||||
|
common.skip('no scrypt support');
|
||||||
|
|
||||||
|
const good = [
|
||||||
|
// Zero-length key is legal, functions as a parameter validation check.
|
||||||
|
{
|
||||||
|
pass: '',
|
||||||
|
salt: '',
|
||||||
|
keylen: 0,
|
||||||
|
N: 16,
|
||||||
|
p: 1,
|
||||||
|
r: 1,
|
||||||
|
expected: '',
|
||||||
|
},
|
||||||
|
// Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that
|
||||||
|
// should pass. Note that the test vector with N=1048576 is omitted
|
||||||
|
// because it takes too long to complete and uses over 1 GB of memory.
|
||||||
|
{
|
||||||
|
pass: '',
|
||||||
|
salt: '',
|
||||||
|
keylen: 64,
|
||||||
|
N: 16,
|
||||||
|
p: 1,
|
||||||
|
r: 1,
|
||||||
|
expected:
|
||||||
|
'77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' +
|
||||||
|
'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pass: 'password',
|
||||||
|
salt: 'NaCl',
|
||||||
|
keylen: 64,
|
||||||
|
N: 1024,
|
||||||
|
p: 16,
|
||||||
|
r: 8,
|
||||||
|
expected:
|
||||||
|
'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' +
|
||||||
|
'2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pass: 'pleaseletmein',
|
||||||
|
salt: 'SodiumChloride',
|
||||||
|
keylen: 64,
|
||||||
|
N: 16384,
|
||||||
|
p: 1,
|
||||||
|
r: 8,
|
||||||
|
expected:
|
||||||
|
'7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' +
|
||||||
|
'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test vectors that should fail.
|
||||||
|
const bad = [
|
||||||
|
{ N: 1, p: 1, r: 1 }, // N < 2
|
||||||
|
{ N: 3, p: 1, r: 1 }, // Not power of 2.
|
||||||
|
{ N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16)
|
||||||
|
{ N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test vectors where 128*N*r exceeds maxmem.
|
||||||
|
const toobig = [
|
||||||
|
{ N: 2 ** 20, p: 1, r: 8 },
|
||||||
|
{ N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const badargs = [
|
||||||
|
{
|
||||||
|
args: [],
|
||||||
|
expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: [null],
|
||||||
|
expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: [''],
|
||||||
|
expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: ['', null],
|
||||||
|
expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: ['', ''],
|
||||||
|
expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: ['', '', null],
|
||||||
|
expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: ['', '', .42],
|
||||||
|
expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: ['', '', -42],
|
||||||
|
expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const options of good) {
|
||||||
|
const { pass, salt, keylen, expected } = options;
|
||||||
|
const actual = crypto.scryptSync(pass, salt, keylen, options);
|
||||||
|
assert.strictEqual(actual.toString('hex'), expected);
|
||||||
|
crypto.scrypt(pass, salt, keylen, options, common.mustCall((err, actual) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.strictEqual(actual.toString('hex'), expected);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const options of bad) {
|
||||||
|
const expected = {
|
||||||
|
code: 'ERR_CRYPTO_SCRYPT_INVALID_PARAMETER',
|
||||||
|
message: 'Invalid scrypt parameter',
|
||||||
|
type: Error,
|
||||||
|
};
|
||||||
|
common.expectsError(() => crypto.scrypt('pass', 'salt', 1, options, () => {}),
|
||||||
|
expected);
|
||||||
|
common.expectsError(() => crypto.scryptSync('pass', 'salt', 1, options),
|
||||||
|
expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const options of toobig) {
|
||||||
|
const expected = {
|
||||||
|
message: /error:[^:]+:digital envelope routines:EVP_PBE_scrypt:memory limit exceeded/,
|
||||||
|
type: Error,
|
||||||
|
};
|
||||||
|
common.expectsError(() => crypto.scrypt('pass', 'salt', 1, options, () => {}),
|
||||||
|
expected);
|
||||||
|
common.expectsError(() => crypto.scryptSync('pass', 'salt', 1, options),
|
||||||
|
expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const defaults = { N: 16384, p: 1, r: 8 };
|
||||||
|
const expected = crypto.scryptSync('pass', 'salt', 1, defaults);
|
||||||
|
const actual = crypto.scryptSync('pass', 'salt', 1);
|
||||||
|
assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex'));
|
||||||
|
crypto.scrypt('pass', 'salt', 1, common.mustCall((err, actual) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex'));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { args, expected } of badargs) {
|
||||||
|
common.expectsError(() => crypto.scrypt(...args), expected);
|
||||||
|
common.expectsError(() => crypto.scryptSync(...args), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const expected = { code: 'ERR_INVALID_CALLBACK' };
|
||||||
|
common.expectsError(() => crypto.scrypt('', '', 42, null), expected);
|
||||||
|
common.expectsError(() => crypto.scrypt('', '', 42, {}, null), expected);
|
||||||
|
common.expectsError(() => crypto.scrypt('', '', 42, {}), expected);
|
||||||
|
common.expectsError(() => crypto.scrypt('', '', 42, {}, {}), expected);
|
||||||
|
}
|
@ -122,6 +122,12 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
|
|||||||
crypto.randomBytes(1, common.mustCall(function rb() {
|
crypto.randomBytes(1, common.mustCall(function rb() {
|
||||||
testInitialized(this, 'RandomBytes');
|
testInitialized(this, 'RandomBytes');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (typeof process.binding('crypto').scrypt === 'function') {
|
||||||
|
crypto.scrypt('password', 'salt', 8, common.mustCall(function() {
|
||||||
|
testInitialized(this, 'AsyncWrap');
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user