| 'use strict'; |
| |
| const { |
| FunctionPrototypeCall, |
| Promise, |
| } = primordials; |
| |
| const { Buffer } = require('buffer'); |
| |
| const { |
| ScryptJob, |
| kCryptoJobAsync, |
| kCryptoJobSync, |
| } = internalBinding('crypto'); |
| |
| const { |
| validateFunction, |
| validateInteger, |
| validateInt32, |
| validateUint32, |
| } = require('internal/validators'); |
| |
| const { |
| codes: { |
| ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, |
| ERR_CRYPTO_SCRYPT_NOT_SUPPORTED, |
| } |
| } = require('internal/errors'); |
| |
| const { |
| getArrayBufferOrView, |
| getDefaultEncoding, |
| kKeyObject, |
| } = require('internal/crypto/util'); |
| |
| const { |
| lazyDOMException, |
| } = require('internal/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); |
| |
| validateFunction(callback, 'callback'); |
| |
| const job = new ScryptJob( |
| kCryptoJobAsync, password, salt, N, r, p, maxmem, keylen); |
| |
| const encoding = getDefaultEncoding(); |
| job.ondone = (error, result) => { |
| if (error !== undefined) |
| return FunctionPrototypeCall(callback, job, error); |
| const buf = Buffer.from(result); |
| if (encoding === 'buffer') |
| return FunctionPrototypeCall(callback, job, null, buf); |
| FunctionPrototypeCall(callback, job, null, buf.toString(encoding)); |
| }; |
| |
| job.run(); |
| } |
| |
| function scryptSync(password, salt, keylen, options = defaults) { |
| options = check(password, salt, keylen, options); |
| const { N, r, p, maxmem } = options; |
| ({ password, salt, keylen } = options); |
| const job = new ScryptJob( |
| kCryptoJobSync, password, salt, N, r, p, maxmem, keylen); |
| const { 0: err, 1: result } = job.run(); |
| |
| if (err !== undefined) |
| throw err; |
| |
| const buf = Buffer.from(result); |
| const encoding = getDefaultEncoding(); |
| return encoding === 'buffer' ? buf : buf.toString(encoding); |
| } |
| |
| function check(password, salt, keylen, options) { |
| if (ScryptJob === undefined) |
| throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED(); |
| |
| password = getArrayBufferOrView(password, 'password'); |
| salt = getArrayBufferOrView(salt, 'salt'); |
| validateInt32(keylen, 'keylen', 0); |
| |
| let { N, r, p, maxmem } = defaults; |
| if (options && options !== defaults) { |
| const has_N = options.N !== undefined; |
| if (has_N) { |
| N = options.N; |
| validateUint32(N, 'N'); |
| } |
| if (options.cost !== undefined) { |
| if (has_N) throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); |
| N = options.cost; |
| validateUint32(N, 'cost'); |
| } |
| const has_r = (options.r !== undefined); |
| if (has_r) { |
| r = options.r; |
| validateUint32(r, 'r'); |
| } |
| if (options.blockSize !== undefined) { |
| if (has_r) throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); |
| r = options.blockSize; |
| validateUint32(r, 'blockSize'); |
| } |
| const has_p = options.p !== undefined; |
| if (has_p) { |
| p = options.p; |
| validateUint32(p, 'p'); |
| } |
| if (options.parallelization !== undefined) { |
| if (has_p) throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); |
| p = options.parallelization; |
| validateUint32(p, 'parallelization'); |
| } |
| if (options.maxmem !== undefined) { |
| maxmem = options.maxmem; |
| validateInteger(maxmem, 'maxmem', 0); |
| } |
| 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 }; |
| } |
| |
| async function scryptDeriveBits(algorithm, baseKey, length) { |
| validateUint32(length, 'length'); |
| const { |
| N = 16384, |
| r = 8, |
| p = 1, |
| maxmem = 32 * 1024 * 1024, |
| encoding, |
| } = algorithm; |
| validateUint32(N, 'algorithm.N'); |
| validateUint32(r, 'algorithm.r'); |
| validateUint32(p, 'algorithm.p'); |
| validateUint32(maxmem, 'algorithm.maxmem'); |
| const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt', encoding); |
| |
| const raw = baseKey[kKeyObject].export(); |
| |
| let byteLength = 64; // the default |
| if (length !== undefined) { |
| if (length === 0) |
| throw lazyDOMException('length cannot be zero', 'OperationError'); |
| if (length % 8) { |
| throw lazyDOMException( |
| 'length must be a multiple of 8', |
| 'OperationError'); |
| } |
| byteLength = length / 8; |
| } |
| |
| return new Promise((resolve, reject) => { |
| scrypt(raw, salt, byteLength, { N, r, p, maxmem }, (err, result) => { |
| if (err) return reject(err); |
| resolve(result.buffer); |
| }); |
| }); |
| } |
| |
| module.exports = { |
| scrypt, |
| scryptSync, |
| scryptDeriveBits, |
| }; |