diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 3d4c1e26c1..a0eaa4abec 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -48,7 +48,60 @@ The `crypto` module provides the `Certificate` class for working with SPKAC data. The most common usage is handling output generated by the HTML5 `` element. Node.js uses [OpenSSL's SPKAC implementation][] internally. -### new crypto.Certificate() +### Certificate.exportChallenge(spkac) + +- `spkac` {string | Buffer | TypedArray | DataView} +- Returns {Buffer} The challenge component of the `spkac` data structure, which +includes a public key and a challenge. + +```js +const { Certificate } = require('crypto'); +const spkac = getSpkacSomehow(); +const challenge = Certificate.exportChallenge(spkac); +console.log(challenge.toString('utf8')); +// Prints: the challenge as a UTF8 string +``` + +### Certificate.exportPublicKey(spkac) + +- `spkac` {string | Buffer | TypedArray | DataView} +- Returns {Buffer} The public key component of the `spkac` data structure, +which includes a public key and a challenge. + +```js +const { Certificate } = require('crypto'); +const spkac = getSpkacSomehow(); +const publicKey = Certificate.exportPublicKey(spkac); +console.log(publicKey); +// Prints: the public key as +``` + +### Certificate.verifySpkac(spkac) + +- `spkac` {Buffer | TypedArray | DataView} +- Returns {boolean} `true` if the given `spkac` data structure is valid, `false` +otherwise. + +```js +const { Certificate } = require('crypto'); +const spkac = getSpkacSomehow(); +console.log(Certificate.verifySpkac(Buffer.from(spkac))); +// Prints: true or false +``` + +### Legacy API + +As a still supported legacy interface, it is possible (but not recommended) to +create new instances of the `crypto.Certificate` class as illustrated in the +examples below. + +#### new crypto.Certificate() Instances of the `Certificate` class can be created using the `new` keyword or by calling `crypto.Certificate()` as a function: @@ -60,7 +113,7 @@ const cert1 = new crypto.Certificate(); const cert2 = crypto.Certificate(); ``` -### certificate.exportChallenge(spkac) +#### certificate.exportChallenge(spkac) @@ -76,7 +129,7 @@ console.log(challenge.toString('utf8')); // Prints: the challenge as a UTF8 string ``` -### certificate.exportPublicKey(spkac) +#### certificate.exportPublicKey(spkac) @@ -92,7 +145,7 @@ console.log(publicKey); // Prints: the public key as ``` -### certificate.verifySpkac(spkac) +#### certificate.verifySpkac(spkac) @@ -1747,9 +1800,13 @@ negative performance implications for some applications, see the ### crypto.randomFillSync(buffer[, offset][, size]) -* `buffer` {Buffer|Uint8Array} Must be supplied. +* `buffer` {Buffer|Uint8Array|ArrayBufferView} Must be supplied. * `offset` {number} Defaults to `0`. * `size` {number} Defaults to `buffer.length - offset`. @@ -1769,12 +1826,29 @@ crypto.randomFillSync(buf, 5, 5); console.log(buf.toString('hex')); ``` +Any `TypedArray` or `DataView` instance may be passed as `buffer`. + +```js +const a = new Uint32Array(10); +console.log(crypto.randomFillSync(a).toString('hex')); + +const b = new Float64Array(10); +console.log(crypto.randomFillSync(a).toString('hex')); + +const c = new DataView(new ArrayBuffer(10)); +console.log(crypto.randomFillSync(a).toString('hex')); +``` + ### crypto.randomFill(buffer[, offset][, size], callback) -* `buffer` {Buffer|Uint8Array} Must be supplied. +* `buffer` {Buffer|Uint8Array|ArrayBufferView} Must be supplied. * `offset` {number} Defaults to `0`. * `size` {number} Defaults to `buffer.length - offset`. * `callback` {Function} `function(err, buf) {}`. @@ -1804,6 +1878,28 @@ crypto.randomFill(buf, 5, 5, (err, buf) => { }); ``` +Any `TypedArray` or `DataView` instance may be passed as `buffer`. + +```js +const a = new Uint32Array(10); +crypto.randomFill(a, (err, buf) => { + if (err) throw err; + console.log(buf.toString('hex')); +}); + +const b = new Float64Array(10); +crypto.randomFill(b, (err, buf) => { + if (err) throw err; + console.log(buf.toString('hex')); +}); + +const c = new DataView(new ArrayBuffer(10)); +crypto.randomFill(c, (err, buf) => { + if (err) throw err; + console.log(buf.toString('hex')); +}); +``` + Note that this API uses libuv's threadpool, which can have surprising and negative performance implications for some applications, see the [`UV_THREADPOOL_SIZE`][] documentation for more information. diff --git a/doc/api/errors.md b/doc/api/errors.md index 8e27abec7a..ba81f2f75e 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -621,6 +621,12 @@ Used when `Console` is instantiated without `stdout` stream or when `stdout` or Used when the native call from `process.cpuUsage` cannot be processed properly. + +### ERR_CRYPTO_ECDH_INVALID_FORMAT + +Used when an invalid value for the `format` argument has been passed to the +`crypto.ECDH()` class `getPublicKey()` method. + ### ERR_DNS_SET_SERVERS_FAILED diff --git a/lib/crypto.js b/lib/crypto.js index 56795e23f2..1c8c6b36fb 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -24,786 +24,144 @@ 'use strict'; -const internalUtil = require('internal/util'); -internalUtil.assertCrypto(); - -exports.DEFAULT_ENCODING = 'buffer'; +const { + assertCrypto, + deprecate +} = require('internal/util'); +assertCrypto(); const constants = process.binding('constants').crypto; -const binding = process.binding('crypto'); -const randomBytes = binding.randomBytes; -const getCiphers = binding.getCiphers; -const getHashes = binding.getHashes; -const getCurves = binding.getCurves; -const getFipsCrypto = binding.getFipsCrypto; -const setFipsCrypto = binding.setFipsCrypto; -const timingSafeEqual = binding.timingSafeEqual; - -const Buffer = require('buffer').Buffer; -const kBufferMaxLength = require('buffer').kMaxLength; -const stream = require('stream'); -const util = require('util'); -const { isUint8Array } = process.binding('util'); -const LazyTransform = require('internal/streams/lazy_transform'); - -const DH_GENERATOR = 2; - -Object.defineProperty(exports, 'constants', { - configurable: false, - enumerable: true, - value: constants -}); - -// This is here because many functions accepted binary strings without -// any explicit encoding in older versions of node, and we don't want -// to break them unnecessarily. -function toBuf(str, encoding) { - if (typeof str === 'string') { - if (encoding === 'buffer' || !encoding) - encoding = 'utf8'; - return Buffer.from(str, encoding); - } - return str; -} -exports._toBuf = toBuf; - - -const assert = require('assert'); -const StringDecoder = require('string_decoder').StringDecoder; - - -exports.createHash = exports.Hash = Hash; -function Hash(algorithm, options) { - if (!(this instanceof Hash)) - return new Hash(algorithm, options); - this._handle = new binding.Hash(algorithm); - LazyTransform.call(this, options); -} - -util.inherits(Hash, LazyTransform); - -Hash.prototype._transform = function _transform(chunk, encoding, callback) { - this._handle.update(chunk, encoding); - callback(); -}; - -Hash.prototype._flush = function _flush(callback) { - this.push(this._handle.digest()); - callback(); -}; - -Hash.prototype.update = function update(data, encoding) { - encoding = encoding || exports.DEFAULT_ENCODING; - this._handle.update(data, encoding); - return this; -}; - - -Hash.prototype.digest = function digest(outputEncoding) { - outputEncoding = outputEncoding || exports.DEFAULT_ENCODING; - // Explicit conversion for backward compatibility. - return this._handle.digest(`${outputEncoding}`); -}; - - -exports.createHmac = exports.Hmac = Hmac; - -function Hmac(hmac, key, options) { - if (!(this instanceof Hmac)) - return new Hmac(hmac, key, options); - this._handle = new binding.Hmac(); - this._handle.init(hmac, toBuf(key)); - LazyTransform.call(this, options); -} - -util.inherits(Hmac, LazyTransform); - -Hmac.prototype.update = Hash.prototype.update; -Hmac.prototype.digest = Hash.prototype.digest; -Hmac.prototype._flush = Hash.prototype._flush; -Hmac.prototype._transform = Hash.prototype._transform; - - -function getDecoder(decoder, encoding) { - encoding = internalUtil.normalizeEncoding(encoding); - decoder = decoder || new StringDecoder(encoding); - assert(decoder.encoding === encoding, 'Cannot change encoding'); - return decoder; -} - - -exports.createCipher = exports.Cipher = Cipher; -function Cipher(cipher, password, options) { - if (!(this instanceof Cipher)) - return new Cipher(cipher, password, options); - this._handle = new binding.CipherBase(true); - - this._handle.init(cipher, toBuf(password)); - this._decoder = null; - - LazyTransform.call(this, options); -} - -util.inherits(Cipher, LazyTransform); - -Cipher.prototype._transform = function _transform(chunk, encoding, callback) { - this.push(this._handle.update(chunk, encoding)); - callback(); -}; - -Cipher.prototype._flush = function _flush(callback) { - try { - this.push(this._handle.final()); - } catch (e) { - callback(e); - return; - } - callback(); -}; - -Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { - inputEncoding = inputEncoding || exports.DEFAULT_ENCODING; - outputEncoding = outputEncoding || exports.DEFAULT_ENCODING; - - var ret = this._handle.update(data, inputEncoding); - - if (outputEncoding && outputEncoding !== 'buffer') { - this._decoder = getDecoder(this._decoder, outputEncoding); - ret = this._decoder.write(ret); - } - - return ret; -}; - - -Cipher.prototype.final = function final(outputEncoding) { - outputEncoding = outputEncoding || exports.DEFAULT_ENCODING; - var ret = this._handle.final(); - - if (outputEncoding && outputEncoding !== 'buffer') { - this._decoder = getDecoder(this._decoder, outputEncoding); - ret = this._decoder.end(ret); - } - - return ret; -}; - - -Cipher.prototype.setAutoPadding = function setAutoPadding(ap) { - this._handle.setAutoPadding(ap); - return this; -}; - -Cipher.prototype.getAuthTag = function getAuthTag() { - return this._handle.getAuthTag(); -}; - - -Cipher.prototype.setAuthTag = function setAuthTag(tagbuf) { - this._handle.setAuthTag(tagbuf); - return this; -}; - -Cipher.prototype.setAAD = function setAAD(aadbuf) { - this._handle.setAAD(aadbuf); - return this; -}; - -exports.createCipheriv = exports.Cipheriv = Cipheriv; -function Cipheriv(cipher, key, iv, options) { - if (!(this instanceof Cipheriv)) - return new Cipheriv(cipher, key, iv, options); - this._handle = new binding.CipherBase(true); - this._handle.initiv(cipher, toBuf(key), toBuf(iv)); - this._decoder = null; - - LazyTransform.call(this, options); -} - -util.inherits(Cipheriv, LazyTransform); - -Cipheriv.prototype._transform = Cipher.prototype._transform; -Cipheriv.prototype._flush = Cipher.prototype._flush; -Cipheriv.prototype.update = Cipher.prototype.update; -Cipheriv.prototype.final = Cipher.prototype.final; -Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; -Cipheriv.prototype.getAuthTag = Cipher.prototype.getAuthTag; -Cipheriv.prototype.setAuthTag = Cipher.prototype.setAuthTag; -Cipheriv.prototype.setAAD = Cipher.prototype.setAAD; - -exports.createDecipher = exports.Decipher = Decipher; -function Decipher(cipher, password, options) { - if (!(this instanceof Decipher)) - return new Decipher(cipher, password, options); - - this._handle = new binding.CipherBase(false); - this._handle.init(cipher, toBuf(password)); - this._decoder = null; - - LazyTransform.call(this, options); -} - -util.inherits(Decipher, LazyTransform); - -Decipher.prototype._transform = Cipher.prototype._transform; -Decipher.prototype._flush = Cipher.prototype._flush; -Decipher.prototype.update = Cipher.prototype.update; -Decipher.prototype.final = Cipher.prototype.final; -Decipher.prototype.finaltol = Cipher.prototype.final; -Decipher.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; -Decipher.prototype.getAuthTag = Cipher.prototype.getAuthTag; -Decipher.prototype.setAuthTag = Cipher.prototype.setAuthTag; -Decipher.prototype.setAAD = Cipher.prototype.setAAD; - - -exports.createDecipheriv = exports.Decipheriv = Decipheriv; -function Decipheriv(cipher, key, iv, options) { - if (!(this instanceof Decipheriv)) - return new Decipheriv(cipher, key, iv, options); - - this._handle = new binding.CipherBase(false); - this._handle.initiv(cipher, toBuf(key), toBuf(iv)); - this._decoder = null; - - LazyTransform.call(this, options); -} - -util.inherits(Decipheriv, LazyTransform); - -Decipheriv.prototype._transform = Cipher.prototype._transform; -Decipheriv.prototype._flush = Cipher.prototype._flush; -Decipheriv.prototype.update = Cipher.prototype.update; -Decipheriv.prototype.final = Cipher.prototype.final; -Decipheriv.prototype.finaltol = Cipher.prototype.final; -Decipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; -Decipheriv.prototype.getAuthTag = Cipher.prototype.getAuthTag; -Decipheriv.prototype.setAuthTag = Cipher.prototype.setAuthTag; -Decipheriv.prototype.setAAD = Cipher.prototype.setAAD; - - -exports.createSign = exports.Sign = Sign; -function Sign(algorithm, options) { - if (!(this instanceof Sign)) - return new Sign(algorithm, options); - this._handle = new binding.Sign(); - this._handle.init(algorithm); - - stream.Writable.call(this, options); -} - -util.inherits(Sign, stream.Writable); - -Sign.prototype._write = function _write(chunk, encoding, callback) { - this._handle.update(chunk, encoding); - callback(); -}; - -Sign.prototype.update = Hash.prototype.update; - -Sign.prototype.sign = function sign(options, encoding) { - if (!options) - throw new Error('No key provided to sign'); - - var key = options.key || options; - var passphrase = options.passphrase || null; - - // Options specific to RSA - var rsaPadding = constants.RSA_PKCS1_PADDING; - if (options.hasOwnProperty('padding')) { - if (options.padding === options.padding >> 0) { - rsaPadding = options.padding; - } else { - throw new TypeError('padding must be an integer'); - } - } - - var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO; - if (options.hasOwnProperty('saltLength')) { - if (options.saltLength === options.saltLength >> 0) { - pssSaltLength = options.saltLength; - } else { - throw new TypeError('saltLength must be an integer'); - } - } - - var ret = this._handle.sign(toBuf(key), passphrase, rsaPadding, - pssSaltLength); - - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - ret = ret.toString(encoding); - - return ret; -}; - - -exports.createVerify = exports.Verify = Verify; -function Verify(algorithm, options) { - if (!(this instanceof Verify)) - return new Verify(algorithm, options); - - this._handle = new binding.Verify(); - this._handle.init(algorithm); - - stream.Writable.call(this, options); -} - -util.inherits(Verify, stream.Writable); - -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; - sigEncoding = sigEncoding || exports.DEFAULT_ENCODING; - - // Options specific to RSA - var rsaPadding = constants.RSA_PKCS1_PADDING; - if (options.hasOwnProperty('padding')) { - if (options.padding === options.padding >> 0) { - rsaPadding = options.padding; - } else { - throw new TypeError('padding must be an integer'); - } - } - - var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO; - if (options.hasOwnProperty('saltLength')) { - if (options.saltLength === options.saltLength >> 0) { - pssSaltLength = options.saltLength; - } else { - throw new TypeError('saltLength must be an integer'); - } - } - - return this._handle.verify(toBuf(key), toBuf(signature, sigEncoding), - rsaPadding, pssSaltLength); -}; - -function rsaPublic(method, defaultPadding) { - return function(options, buffer) { - var key = options.key || options; - var padding = options.padding || defaultPadding; - var passphrase = options.passphrase || null; - return method(toBuf(key), buffer, padding, passphrase); - }; -} - -function rsaPrivate(method, defaultPadding) { - return function(options, buffer) { - var key = options.key || options; - var passphrase = options.passphrase || null; - var padding = options.padding || defaultPadding; - return method(toBuf(key), buffer, padding, passphrase); - }; +const { + getFipsCrypto, + setFipsCrypto, + timingSafeEqual +} = process.binding('crypto'); +const { + randomBytes, + randomFill, + randomFillSync +} = require('internal/crypto/random'); +const { + pbkdf2, + pbkdf2Sync +} = require('internal/crypto/pbkdf2'); +const { + DiffieHellman, + DiffieHellmanGroup, + ECDH +} = require('internal/crypto/diffiehellman'); +const { + Cipher, + Cipheriv, + Decipher, + Decipheriv, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt +} = require('internal/crypto/cipher'); +const { + Sign, + Verify +} = require('internal/crypto/sig'); +const { + Hash, + Hmac +} = require('internal/crypto/hash'); +const { + getCiphers, + getCurves, + getDefaultEncoding, + getHashes, + setDefaultEncoding, + setEngine, + toBuf +} = require('internal/crypto/util'); +const Certificate = require('internal/crypto/certificate'); + +function createECDH(curve) { + return new ECDH(curve); } -exports.publicEncrypt = rsaPublic(binding.publicEncrypt, - constants.RSA_PKCS1_OAEP_PADDING); -exports.publicDecrypt = rsaPublic(binding.publicDecrypt, - constants.RSA_PKCS1_PADDING); -exports.privateEncrypt = rsaPrivate(binding.privateEncrypt, - constants.RSA_PKCS1_PADDING); -exports.privateDecrypt = rsaPrivate(binding.privateDecrypt, - constants.RSA_PKCS1_OAEP_PADDING); - - -exports.createDiffieHellman = exports.DiffieHellman = DiffieHellman; - -function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) { - if (!(this instanceof DiffieHellman)) - return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); - - if (typeof sizeOrKey !== 'number' && - typeof sizeOrKey !== 'string' && - !ArrayBuffer.isView(sizeOrKey)) { - throw new TypeError('First argument should be number, string, ' + - 'Buffer, TypedArray, or DataView'); - } - - if (keyEncoding) { - if (typeof keyEncoding !== 'string' || - (!Buffer.isEncoding(keyEncoding) && keyEncoding !== 'buffer')) { - genEncoding = generator; - generator = keyEncoding; - keyEncoding = false; - } - } - - keyEncoding = keyEncoding || exports.DEFAULT_ENCODING; - genEncoding = genEncoding || exports.DEFAULT_ENCODING; - - if (typeof sizeOrKey !== 'number') - sizeOrKey = toBuf(sizeOrKey, keyEncoding); - - if (!generator) - generator = DH_GENERATOR; - else if (typeof generator !== 'number') - generator = toBuf(generator, genEncoding); - - this._handle = new binding.DiffieHellman(sizeOrKey, generator); - Object.defineProperty(this, 'verifyError', { +module.exports = exports = { + // Methods + _toBuf: toBuf, + createCipher: Cipher, + createCipheriv: Cipheriv, + createDecipher: Decipher, + createDecipheriv: Decipheriv, + createDiffieHellman: DiffieHellman, + createDiffieHellmanGroup: DiffieHellmanGroup, + createECDH, + createHash: Hash, + createHmac: Hmac, + createSign: Sign, + createVerify: Verify, + getCiphers, + getCurves, + getDiffieHellman: DiffieHellmanGroup, + getHashes, + pbkdf2, + pbkdf2Sync, + privateDecrypt, + privateEncrypt, + prng: randomBytes, + pseudoRandomBytes: randomBytes, + publicDecrypt, + publicEncrypt, + randomBytes, + randomFill, + randomFillSync, + rng: randomBytes, + setEngine, + timingSafeEqual, + + // Classes + Certificate, + Cipher, + Cipheriv, + Decipher, + Decipheriv, + DiffieHellman, + DiffieHellmanGroup, + Hash, + Hmac, + Sign, + Verify +}; + +Object.defineProperties(exports, { + fips: { + get: getFipsCrypto, + set: setFipsCrypto + }, + DEFAULT_ENCODING: { enumerable: true, - value: this._handle.verifyError, - writable: false - }); -} - - -exports.DiffieHellmanGroup = - exports.createDiffieHellmanGroup = - exports.getDiffieHellman = DiffieHellmanGroup; - -function DiffieHellmanGroup(name) { - if (!(this instanceof DiffieHellmanGroup)) - return new DiffieHellmanGroup(name); - this._handle = new binding.DiffieHellmanGroup(name); - Object.defineProperty(this, 'verifyError', { + configurable: true, + get: getDefaultEncoding, + set: setDefaultEncoding + }, + constants: { + configurable: false, enumerable: true, - value: this._handle.verifyError, - writable: false - }); -} - - -DiffieHellmanGroup.prototype.generateKeys = - DiffieHellman.prototype.generateKeys = - dhGenerateKeys; - -function dhGenerateKeys(encoding) { - var keys = this._handle.generateKeys(); - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - keys = keys.toString(encoding); - return keys; -} - - -DiffieHellmanGroup.prototype.computeSecret = - DiffieHellman.prototype.computeSecret = - dhComputeSecret; - -function dhComputeSecret(key, inEnc, outEnc) { - inEnc = inEnc || exports.DEFAULT_ENCODING; - outEnc = outEnc || exports.DEFAULT_ENCODING; - var ret = this._handle.computeSecret(toBuf(key, inEnc)); - if (outEnc && outEnc !== 'buffer') - ret = ret.toString(outEnc); - return ret; -} - - -DiffieHellmanGroup.prototype.getPrime = - DiffieHellman.prototype.getPrime = - dhGetPrime; - -function dhGetPrime(encoding) { - var prime = this._handle.getPrime(); - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - prime = prime.toString(encoding); - return prime; -} - - -DiffieHellmanGroup.prototype.getGenerator = - DiffieHellman.prototype.getGenerator = - dhGetGenerator; - -function dhGetGenerator(encoding) { - var generator = this._handle.getGenerator(); - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - generator = generator.toString(encoding); - return generator; -} - - -DiffieHellmanGroup.prototype.getPublicKey = - DiffieHellman.prototype.getPublicKey = - dhGetPublicKey; - -function dhGetPublicKey(encoding) { - var key = this._handle.getPublicKey(); - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - key = key.toString(encoding); - return key; -} - - -DiffieHellmanGroup.prototype.getPrivateKey = - DiffieHellman.prototype.getPrivateKey = - dhGetPrivateKey; - -function dhGetPrivateKey(encoding) { - var key = this._handle.getPrivateKey(); - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - key = key.toString(encoding); - return key; -} + value: constants + }, - -DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) { - encoding = encoding || exports.DEFAULT_ENCODING; - this._handle.setPublicKey(toBuf(key, encoding)); - return this; -}; - - -DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { - encoding = encoding || exports.DEFAULT_ENCODING; - this._handle.setPrivateKey(toBuf(key, encoding)); - return this; -}; - - -function ECDH(curve) { - if (typeof curve !== 'string') - throw new TypeError('"curve" argument should be a string'); - - this._handle = new binding.ECDH(curve); -} - -exports.createECDH = function createECDH(curve) { - return new ECDH(curve); -}; - -ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret; -ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey; -ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey; -ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey; - -ECDH.prototype.generateKeys = function generateKeys(encoding, format) { - this._handle.generateKeys(); - - return this.getPublicKey(encoding, format); -}; - -ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) { - var f; - if (format) { - if (typeof format === 'number') - f = format; - if (format === 'compressed') - f = constants.POINT_CONVERSION_COMPRESSED; - else if (format === 'hybrid') - f = constants.POINT_CONVERSION_HYBRID; - // Default - else if (format === 'uncompressed') - f = constants.POINT_CONVERSION_UNCOMPRESSED; - else - throw new TypeError('Bad format: ' + format); - } else { - f = constants.POINT_CONVERSION_UNCOMPRESSED; - } - var key = this._handle.getPublicKey(f); - encoding = encoding || exports.DEFAULT_ENCODING; - if (encoding && encoding !== 'buffer') - key = key.toString(encoding); - return key; -}; - - -exports.pbkdf2 = function(password, - salt, - iterations, - keylen, - digest, - callback) { - if (typeof digest === 'function') { - callback = digest; - digest = undefined; - } - - if (typeof callback !== 'function') - throw new Error('No callback provided to pbkdf2'); - - return pbkdf2(password, salt, iterations, keylen, digest, callback); -}; - - -exports.pbkdf2Sync = function(password, salt, iterations, keylen, digest) { - return pbkdf2(password, salt, iterations, keylen, digest); -}; - - -function pbkdf2(password, salt, iterations, keylen, digest, callback) { - - if (digest === undefined) { - throw new TypeError( - 'The "digest" argument is required and must not be undefined'); - } - - password = toBuf(password); - salt = toBuf(salt); - - if (exports.DEFAULT_ENCODING === 'buffer') - return binding.PBKDF2(password, salt, iterations, keylen, digest, callback); - - // at this point, we need to handle encodings. - var encoding = exports.DEFAULT_ENCODING; - if (callback) { - function next(er, ret) { - if (ret) - ret = ret.toString(encoding); - callback(er, ret); - } - binding.PBKDF2(password, salt, iterations, keylen, digest, next); - } else { - var ret = binding.PBKDF2(password, salt, iterations, keylen, digest); - return ret.toString(encoding); - } -} - - -exports.Certificate = Certificate; - -function Certificate() { - if (!(this instanceof Certificate)) - return new Certificate(); -} - - -Certificate.prototype.verifySpkac = function verifySpkac(object) { - return binding.certVerifySpkac(object); -}; - - -Certificate.prototype.exportPublicKey = - function exportPublicKey(object, encoding) { - return binding.certExportPublicKey(toBuf(object, encoding)); - }; - - -Certificate.prototype.exportChallenge = - function exportChallenge(object, encoding) { - return binding.certExportChallenge(toBuf(object, encoding)); - }; - - -exports.setEngine = function setEngine(id, flags) { - if (typeof id !== 'string') - throw new TypeError('"id" argument should be a string'); - - if (flags && typeof flags !== 'number') - throw new TypeError('"flags" argument should be a number, if present'); - flags = flags >>> 0; - - // Use provided engine for everything by default - if (flags === 0) - flags = constants.ENGINE_METHOD_ALL; - - return binding.setEngine(id, flags); -}; - -const kMaxUint32 = Math.pow(2, 32) - 1; - -function randomFillSync(buf, offset = 0, size) { - if (!isUint8Array(buf)) { - throw new TypeError('"buf" argument must be a Buffer or Uint8Array'); - } - - assertOffset(offset, buf.length); - - if (size === undefined) size = buf.length - offset; - - assertSize(size, offset, buf.length); - - return binding.randomFill(buf, offset, size); -} -exports.randomFillSync = randomFillSync; - -function randomFill(buf, offset, size, cb) { - if (!isUint8Array(buf)) { - throw new TypeError('"buf" argument must be a Buffer or Uint8Array'); - } - - if (typeof offset === 'function') { - cb = offset; - offset = 0; - size = buf.length; - } else if (typeof size === 'function') { - cb = size; - size = buf.length - offset; - } else if (typeof cb !== 'function') { - throw new TypeError('"cb" argument must be a function'); - } - - assertOffset(offset, buf.length); - assertSize(size, offset, buf.length); - - return binding.randomFill(buf, offset, size, cb); -} -exports.randomFill = randomFill; - -function assertOffset(offset, length) { - if (typeof offset !== 'number' || offset !== offset) { - throw new TypeError('offset must be a number'); - } - - if (offset > kMaxUint32 || offset < 0) { - throw new TypeError('offset must be a uint32'); - } - - if (offset > kBufferMaxLength || offset > length) { - throw new RangeError('offset out of range'); - } -} - -function assertSize(size, offset, length) { - if (typeof size !== 'number' || size !== size) { - throw new TypeError('size must be a number'); - } - - if (size > kMaxUint32 || size < 0) { - throw new TypeError('size must be a uint32'); - } - - if (size + offset > length || size > kBufferMaxLength) { - throw new RangeError('buffer too small'); + // Legacy API + createCredentials: { + configurable: true, + enumerable: true, + get: deprecate(() => { + return require('tls').createSecureContext; + }, 'crypto.createCredentials is deprecated. ' + + 'Use tls.createSecureContext instead.', 'DEP0010') + }, + Credentials: { + configurable: true, + enumerable: true, + get: deprecate(function() { + return require('tls').SecureContext; + }, 'crypto.Credentials is deprecated. ' + + 'Use tls.SecureContext instead.', 'DEP0011') } -} - -exports.randomBytes = exports.pseudoRandomBytes = randomBytes; - -exports.rng = exports.prng = randomBytes; - -exports.getCiphers = internalUtil.cachedResult( - () => internalUtil.filterDuplicateStrings(getCiphers()) -); - -exports.getHashes = internalUtil.cachedResult( - () => internalUtil.filterDuplicateStrings(getHashes()) -); - -exports.getCurves = internalUtil.cachedResult( - () => internalUtil.filterDuplicateStrings(getCurves()) -); - -Object.defineProperty(exports, 'fips', { - get: getFipsCrypto, - set: setFipsCrypto -}); - -exports.timingSafeEqual = timingSafeEqual; - -// Legacy API -Object.defineProperty(exports, 'createCredentials', { - configurable: true, - enumerable: true, - get: internalUtil.deprecate(function() { - return require('tls').createSecureContext; - }, 'crypto.createCredentials is deprecated. ' + - 'Use tls.createSecureContext instead.', 'DEP0010') -}); - -Object.defineProperty(exports, 'Credentials', { - configurable: true, - enumerable: true, - get: internalUtil.deprecate(function() { - return require('tls').SecureContext; - }, 'crypto.Credentials is deprecated. ' + - 'Use tls.SecureContext instead.', 'DEP0011') }); diff --git a/lib/internal/crypto/certificate.js b/lib/internal/crypto/certificate.js new file mode 100644 index 0000000000..e37bedd2f9 --- /dev/null +++ b/lib/internal/crypto/certificate.js @@ -0,0 +1,40 @@ +'use strict'; + +const { + certExportChallenge, + certExportPublicKey, + certVerifySpkac +} = process.binding('crypto'); + +const { + toBuf +} = require('internal/crypto/util'); + +function verifySpkac(object) { + return certVerifySpkac(object); +} + +function exportPublicKey(object, encoding) { + return certExportPublicKey(toBuf(object, encoding)); +} + +function exportChallenge(object, encoding) { + return certExportChallenge(toBuf(object, encoding)); +} + +// For backwards compatibility reasons, this cannot be converted into a +// ES6 Class. +function Certificate() { + if (!(this instanceof Certificate)) + return new Certificate(); +} + +Certificate.prototype.verifySpkac = verifySpkac; +Certificate.prototype.exportPublicKey = exportPublicKey; +Certificate.prototype.exportChallenge = exportChallenge; + +Certificate.exportChallenge = exportChallenge; +Certificate.exportPublicKey = exportPublicKey; +Certificate.verifySpkac = verifySpkac; + +module.exports = Certificate; diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js new file mode 100644 index 0000000000..d9b31674c1 --- /dev/null +++ b/lib/internal/crypto/cipher.js @@ -0,0 +1,214 @@ +'use strict'; + +const { + RSA_PKCS1_OAEP_PADDING, + RSA_PKCS1_PADDING +} = process.binding('constants').crypto; + +const { + getDefaultEncoding, + toBuf +} = require('internal/crypto/util'); + +const { + CipherBase, + privateDecrypt: _privateDecrypt, + privateEncrypt: _privateEncrypt, + publicDecrypt: _publicDecrypt, + publicEncrypt: _publicEncrypt +} = process.binding('crypto'); + +const assert = require('assert'); +const LazyTransform = require('internal/streams/lazy_transform'); +const { StringDecoder } = require('string_decoder'); + +const { inherits } = require('util'); +const { normalizeEncoding } = require('internal/util'); + +function rsaPublic(method, defaultPadding) { + return function(options, buffer) { + const key = options.key || options; + const padding = options.padding || defaultPadding; + const passphrase = options.passphrase || null; + return method(toBuf(key), buffer, padding, passphrase); + }; +} + +function rsaPrivate(method, defaultPadding) { + return function(options, buffer) { + const key = options.key || options; + const passphrase = options.passphrase || null; + const padding = options.padding || defaultPadding; + return method(toBuf(key), buffer, padding, passphrase); + }; +} + +const publicEncrypt = rsaPublic(_publicEncrypt, RSA_PKCS1_OAEP_PADDING); +const publicDecrypt = rsaPublic(_publicDecrypt, RSA_PKCS1_PADDING); +const privateEncrypt = rsaPrivate(_privateEncrypt, RSA_PKCS1_PADDING); +const privateDecrypt = rsaPrivate(_privateDecrypt, RSA_PKCS1_OAEP_PADDING); + +function getDecoder(decoder, encoding) { + encoding = normalizeEncoding(encoding); + decoder = decoder || new StringDecoder(encoding); + assert(decoder.encoding === encoding, 'Cannot change encoding'); + return decoder; +} + +function Cipher(cipher, password, options) { + if (!(this instanceof Cipher)) + return new Cipher(cipher, password, options); + this._handle = new CipherBase(true); + + this._handle.init(cipher, toBuf(password)); + this._decoder = null; + + LazyTransform.call(this, options); +} + +inherits(Cipher, LazyTransform); + +Cipher.prototype._transform = function _transform(chunk, encoding, callback) { + this.push(this._handle.update(chunk, encoding)); + callback(); +}; + +Cipher.prototype._flush = function _flush(callback) { + try { + this.push(this._handle.final()); + } catch (e) { + callback(e); + return; + } + callback(); +}; + +Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { + const encoding = getDefaultEncoding(); + inputEncoding = inputEncoding || encoding; + outputEncoding = outputEncoding || encoding; + + var ret = this._handle.update(data, inputEncoding); + + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding); + ret = this._decoder.write(ret); + } + + return ret; +}; + + +Cipher.prototype.final = function final(outputEncoding) { + outputEncoding = outputEncoding || getDefaultEncoding(); + var ret = this._handle.final(); + + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding); + ret = this._decoder.end(ret); + } + + return ret; +}; + + +Cipher.prototype.setAutoPadding = function setAutoPadding(ap) { + this._handle.setAutoPadding(ap); + return this; +}; + +Cipher.prototype.getAuthTag = function getAuthTag() { + return this._handle.getAuthTag(); +}; + + +Cipher.prototype.setAuthTag = function setAuthTag(tagbuf) { + this._handle.setAuthTag(tagbuf); + return this; +}; + +Cipher.prototype.setAAD = function setAAD(aadbuf) { + this._handle.setAAD(aadbuf); + return this; +}; + +function Cipheriv(cipher, key, iv, options) { + if (!(this instanceof Cipheriv)) + return new Cipheriv(cipher, key, iv, options); + this._handle = new CipherBase(true); + this._handle.initiv(cipher, toBuf(key), toBuf(iv)); + this._decoder = null; + + LazyTransform.call(this, options); +} + +inherits(Cipheriv, LazyTransform); + +Cipheriv.prototype._transform = Cipher.prototype._transform; +Cipheriv.prototype._flush = Cipher.prototype._flush; +Cipheriv.prototype.update = Cipher.prototype.update; +Cipheriv.prototype.final = Cipher.prototype.final; +Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; +Cipheriv.prototype.getAuthTag = Cipher.prototype.getAuthTag; +Cipheriv.prototype.setAuthTag = Cipher.prototype.setAuthTag; +Cipheriv.prototype.setAAD = Cipher.prototype.setAAD; + + +function Decipher(cipher, password, options) { + if (!(this instanceof Decipher)) + return new Decipher(cipher, password, options); + + this._handle = new CipherBase(false); + this._handle.init(cipher, toBuf(password)); + this._decoder = null; + + LazyTransform.call(this, options); +} + +inherits(Decipher, LazyTransform); + +Decipher.prototype._transform = Cipher.prototype._transform; +Decipher.prototype._flush = Cipher.prototype._flush; +Decipher.prototype.update = Cipher.prototype.update; +Decipher.prototype.final = Cipher.prototype.final; +Decipher.prototype.finaltol = Cipher.prototype.final; +Decipher.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; +Decipher.prototype.getAuthTag = Cipher.prototype.getAuthTag; +Decipher.prototype.setAuthTag = Cipher.prototype.setAuthTag; +Decipher.prototype.setAAD = Cipher.prototype.setAAD; + + +function Decipheriv(cipher, key, iv, options) { + if (!(this instanceof Decipheriv)) + return new Decipheriv(cipher, key, iv, options); + + this._handle = new CipherBase(false); + this._handle.initiv(cipher, toBuf(key), toBuf(iv)); + this._decoder = null; + + LazyTransform.call(this, options); +} + +inherits(Decipheriv, LazyTransform); + +Decipheriv.prototype._transform = Cipher.prototype._transform; +Decipheriv.prototype._flush = Cipher.prototype._flush; +Decipheriv.prototype.update = Cipher.prototype.update; +Decipheriv.prototype.final = Cipher.prototype.final; +Decipheriv.prototype.finaltol = Cipher.prototype.final; +Decipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; +Decipheriv.prototype.getAuthTag = Cipher.prototype.getAuthTag; +Decipheriv.prototype.setAuthTag = Cipher.prototype.setAuthTag; +Decipheriv.prototype.setAAD = Cipher.prototype.setAAD; + + +module.exports = { + Cipher, + Cipheriv, + Decipher, + Decipheriv, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, +}; diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js new file mode 100644 index 0000000000..b891a0b354 --- /dev/null +++ b/lib/internal/crypto/diffiehellman.js @@ -0,0 +1,216 @@ +'use strict'; + +const { Buffer } = require('buffer'); +const errors = require('internal/errors'); +const { + getDefaultEncoding, + toBuf +} = require('internal/crypto/util'); +const { + DiffieHellman: _DiffieHellman, + DiffieHellmanGroup: _DiffieHellmanGroup, + ECDH: _ECDH +} = process.binding('crypto'); +const { + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_HYBRID, + POINT_CONVERSION_UNCOMPRESSED +} = process.binding('constants').crypto; + +const DH_GENERATOR = 2; + +function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) { + if (!(this instanceof DiffieHellman)) + return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); + + if (typeof sizeOrKey !== 'number' && + typeof sizeOrKey !== 'string' && + !ArrayBuffer.isView(sizeOrKey)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'sizeOrKey', + ['number', 'string', 'Buffer', 'TypedArray', + 'DataView']); + } + + if (keyEncoding) { + if (typeof keyEncoding !== 'string' || + (!Buffer.isEncoding(keyEncoding) && keyEncoding !== 'buffer')) { + genEncoding = generator; + generator = keyEncoding; + keyEncoding = false; + } + } + + const encoding = getDefaultEncoding(); + keyEncoding = keyEncoding || encoding; + genEncoding = genEncoding || encoding; + + if (typeof sizeOrKey !== 'number') + sizeOrKey = toBuf(sizeOrKey, keyEncoding); + + if (!generator) + generator = DH_GENERATOR; + else if (typeof generator !== 'number') + generator = toBuf(generator, genEncoding); + + this._handle = new _DiffieHellman(sizeOrKey, generator); + Object.defineProperty(this, 'verifyError', { + enumerable: true, + value: this._handle.verifyError, + writable: false + }); +} + + +function DiffieHellmanGroup(name) { + if (!(this instanceof DiffieHellmanGroup)) + return new DiffieHellmanGroup(name); + this._handle = new _DiffieHellmanGroup(name); + Object.defineProperty(this, 'verifyError', { + enumerable: true, + value: this._handle.verifyError, + writable: false + }); +} + + +DiffieHellmanGroup.prototype.generateKeys = + DiffieHellman.prototype.generateKeys = + dhGenerateKeys; + +function dhGenerateKeys(encoding) { + var keys = this._handle.generateKeys(); + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + keys = keys.toString(encoding); + return keys; +} + + +DiffieHellmanGroup.prototype.computeSecret = + DiffieHellman.prototype.computeSecret = + dhComputeSecret; + +function dhComputeSecret(key, inEnc, outEnc) { + const encoding = getDefaultEncoding(); + inEnc = inEnc || encoding; + outEnc = outEnc || encoding; + var ret = this._handle.computeSecret(toBuf(key, inEnc)); + if (outEnc && outEnc !== 'buffer') + ret = ret.toString(outEnc); + return ret; +} + + +DiffieHellmanGroup.prototype.getPrime = + DiffieHellman.prototype.getPrime = + dhGetPrime; + +function dhGetPrime(encoding) { + var prime = this._handle.getPrime(); + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + prime = prime.toString(encoding); + return prime; +} + + +DiffieHellmanGroup.prototype.getGenerator = + DiffieHellman.prototype.getGenerator = + dhGetGenerator; + +function dhGetGenerator(encoding) { + var generator = this._handle.getGenerator(); + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + generator = generator.toString(encoding); + return generator; +} + + +DiffieHellmanGroup.prototype.getPublicKey = + DiffieHellman.prototype.getPublicKey = + dhGetPublicKey; + +function dhGetPublicKey(encoding) { + var key = this._handle.getPublicKey(); + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; +} + + +DiffieHellmanGroup.prototype.getPrivateKey = + DiffieHellman.prototype.getPrivateKey = + dhGetPrivateKey; + +function dhGetPrivateKey(encoding) { + var key = this._handle.getPrivateKey(); + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; +} + + +DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) { + encoding = encoding || getDefaultEncoding(); + this._handle.setPublicKey(toBuf(key, encoding)); + return this; +}; + + +DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { + encoding = encoding || getDefaultEncoding(); + this._handle.setPrivateKey(toBuf(key, encoding)); + return this; +}; + + +function ECDH(curve) { + if (typeof curve !== 'string') + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'curve', 'string'); + + this._handle = new _ECDH(curve); +} + +ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret; +ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey; +ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey; +ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey; + +ECDH.prototype.generateKeys = function generateKeys(encoding, format) { + this._handle.generateKeys(); + + return this.getPublicKey(encoding, format); +}; + +ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) { + var f; + if (format) { + if (typeof format === 'number') + f = format; + if (format === 'compressed') + f = POINT_CONVERSION_COMPRESSED; + else if (format === 'hybrid') + f = POINT_CONVERSION_HYBRID; + // Default + else if (format === 'uncompressed') + f = POINT_CONVERSION_UNCOMPRESSED; + else + throw new errors.TypeError('ERR_CRYPTO_ECDH_INVALID_FORMAT', format); + } else { + f = POINT_CONVERSION_UNCOMPRESSED; + } + var key = this._handle.getPublicKey(f); + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; +}; + +module.exports = { + DiffieHellman, + DiffieHellmanGroup, + ECDH +}; diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js new file mode 100644 index 0000000000..12b3e1e78e --- /dev/null +++ b/lib/internal/crypto/hash.js @@ -0,0 +1,125 @@ +'use strict'; + +const { + Hash: _Hash, + Hmac: _Hmac +} = process.binding('crypto'); + +const { + getDefaultEncoding, + toBuf +} = require('internal/crypto/util'); + +const { + isArrayBufferView +} = process.binding('util'); + +const { Buffer } = require('buffer'); + +const errors = require('internal/errors'); +const { inherits } = require('util'); +const { normalizeEncoding } = require('internal/util'); +const LazyTransform = require('internal/streams/lazy_transform'); +const kState = Symbol('state'); +const kFinalized = Symbol('finalized'); + +function Hash(algorithm, options) { + if (!(this instanceof Hash)) + return new Hash(algorithm, options); + if (typeof algorithm !== 'string') + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'algorithm', 'string'); + this._handle = new _Hash(algorithm); + this[kState] = { + [kFinalized]: false + }; + LazyTransform.call(this, options); +} + +inherits(Hash, LazyTransform); + +Hash.prototype._transform = function _transform(chunk, encoding, callback) { + this._handle.update(chunk, encoding); + callback(); +}; + +Hash.prototype._flush = function _flush(callback) { + this.push(this._handle.digest()); + callback(); +}; + +Hash.prototype.update = function update(data, encoding) { + const state = this[kState]; + if (state[kFinalized]) + throw new errors.Error('ERR_CRYPTO_HASH_FINALIZED'); + + if (typeof data !== 'string' && !isArrayBufferView(data)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'data', + ['string', 'TypedArray', 'DataView']); + } + + if (!this._handle.update(data, encoding || getDefaultEncoding())) + throw new errors.Error('ERR_CRYPTO_HASH_UPDATE_FAILED'); + return this; +}; + + +Hash.prototype.digest = function digest(outputEncoding) { + const state = this[kState]; + if (state[kFinalized]) + throw new errors.Error('ERR_CRYPTO_HASH_FINALIZED'); + outputEncoding = outputEncoding || getDefaultEncoding(); + if (normalizeEncoding(outputEncoding) === 'utf16le') + throw new errors.Error('ERR_CRYPTO_HASH_DIGEST_NO_UTF16'); + + // Explicit conversion for backward compatibility. + const ret = this._handle.digest(`${outputEncoding}`); + state[kFinalized] = true; + return ret; +}; + + +function Hmac(hmac, key, options) { + if (!(this instanceof Hmac)) + return new Hmac(hmac, key, options); + if (typeof hmac !== 'string') + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'hmac', 'string'); + if (typeof key !== 'string' && !isArrayBufferView(key)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'key', + ['string', 'TypedArray', 'DataView']); + } + this._handle = new _Hmac(); + this._handle.init(hmac, toBuf(key)); + this[kState] = { + [kFinalized]: false + }; + LazyTransform.call(this, options); +} + +inherits(Hmac, LazyTransform); + +Hmac.prototype.update = Hash.prototype.update; + +Hmac.prototype.digest = function digest(outputEncoding) { + const state = this[kState]; + outputEncoding = outputEncoding || getDefaultEncoding(); + if (normalizeEncoding(outputEncoding) === 'utf16le') + throw new errors.Error('ERR_CRYPTO_HASH_DIGEST_NO_UTF16'); + + if (state[kFinalized]) { + const buf = Buffer.from(''); + return outputEncoding === 'buffer' ? buf : buf.toString(outputEncoding); + } + + // Explicit conversion for backward compatibility. + const ret = this._handle.digest(`${outputEncoding}`); + state[kFinalized] = true; + return ret; +}; + +Hmac.prototype._flush = Hash.prototype._flush; +Hmac.prototype._transform = Hash.prototype._transform; + +module.exports = { + Hash, + Hmac +}; diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js new file mode 100644 index 0000000000..5398321ece --- /dev/null +++ b/lib/internal/crypto/pbkdf2.js @@ -0,0 +1,59 @@ +'use strict'; + +const errors = require('internal/errors'); +const { + getDefaultEncoding, + toBuf +} = require('internal/crypto/util'); +const { + PBKDF2 +} = process.binding('crypto'); + +function pbkdf2(password, salt, iterations, keylen, digest, callback) { + if (typeof digest === 'function') { + callback = digest; + digest = undefined; + } + + if (typeof callback !== 'function') + throw new errors.TypeError('ERR_INVALID_CALLBACK'); + + return _pbkdf2(password, salt, iterations, keylen, digest, callback); +} + +function pbkdf2Sync(password, salt, iterations, keylen, digest) { + return _pbkdf2(password, salt, iterations, keylen, digest); +} + +function _pbkdf2(password, salt, iterations, keylen, digest, callback) { + + if (digest !== null && typeof digest !== 'string') + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'digest', + ['string', 'null']); + + password = toBuf(password); + salt = toBuf(salt); + + const encoding = getDefaultEncoding(); + + if (encoding === 'buffer') + return PBKDF2(password, salt, iterations, keylen, digest, callback); + + // at this point, we need to handle encodings. + if (callback) { + function next(er, ret) { + if (ret) + ret = ret.toString(encoding); + callback(er, ret); + } + PBKDF2(password, salt, iterations, keylen, digest, next); + } else { + var ret = PBKDF2(password, salt, iterations, keylen, digest); + return ret.toString(encoding); + } +} + +module.exports = { + pbkdf2, + pbkdf2Sync +}; diff --git a/lib/internal/crypto/random.js b/lib/internal/crypto/random.js new file mode 100644 index 0000000000..81025289d5 --- /dev/null +++ b/lib/internal/crypto/random.js @@ -0,0 +1,98 @@ +'use strict'; + +const errors = require('internal/errors'); +const { isArrayBufferView } = process.binding('util'); +const { + randomBytes, + randomFill: _randomFill +} = process.binding('crypto'); + +const { kMaxLength } = require('buffer'); +const kMaxUint32 = Math.pow(2, 32) - 1; + +function assertOffset(offset, length) { + if (typeof offset !== 'number' || offset !== offset) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'offset', 'number'); + } + + if (offset > kMaxUint32 || offset < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'offset', 'uint32'); + } + + if (offset > kMaxLength || offset > length) { + throw new errors.RangeError('ERR_OUT_OF_RANGE', 'offset'); + } +} + +function assertSize(size, offset, length) { + if (typeof size !== 'number' || size !== size) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'size', 'number'); + } + + if (size > kMaxUint32 || size < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'size', 'uint32'); + } + + if (size + offset > length || size > kMaxLength) { + throw new errors.RangeError('ERR_OUT_OF_RANGE', 'size'); + } +} + +function randomFillSync(buf, offset = 0, size) { + if (!isArrayBufferView(buf)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + 'buf', 'ArrayBufferView'); + } + + const elementSize = buf.BYTES_PER_ELEMENT || 1; + + offset *= elementSize; + assertOffset(offset, buf.byteLength); + + if (size === undefined) { + size = buf.byteLength - offset; + } else { + size *= elementSize; + } + + assertSize(size, offset, buf.byteLength); + + return _randomFill(buf, offset, size); +} + +function randomFill(buf, offset, size, cb) { + if (!isArrayBufferView(buf)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + 'buf', 'ArrayBufferView'); + } + + const elementSize = buf.BYTES_PER_ELEMENT || 1; + + if (typeof offset === 'function') { + cb = offset; + offset = 0; + size = buf.bytesLength; + } else if (typeof size === 'function') { + cb = size; + offset *= elementSize; + size = buf.byteLength - offset; + } else if (typeof cb !== 'function') { + throw new errors.TypeError('ERR_INVALID_CALLBACK'); + } + if (size === undefined) { + size = buf.byteLength - offset; + } else { + size *= elementSize; + } + + assertOffset(offset, buf.byteLength); + assertSize(size, offset, buf.byteLength); + + return _randomFill(buf, offset, size, cb); +} + +module.exports = { + randomBytes, + randomFill, + randomFillSync +}; diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js new file mode 100644 index 0000000000..52827c9c4b --- /dev/null +++ b/lib/internal/crypto/sig.js @@ -0,0 +1,131 @@ +'use strict'; + +const errors = require('internal/errors'); +const { + Sign: _Sign, + Verify: _Verify +} = process.binding('crypto'); +const { + RSA_PSS_SALTLEN_AUTO, + RSA_PKCS1_PADDING +} = process.binding('constants').crypto; +const { + getDefaultEncoding, + toBuf +} = require('internal/crypto/util'); +const { Writable } = require('stream'); +const { inherits } = require('util'); + +function Sign(algorithm, options) { + if (!(this instanceof Sign)) + return new Sign(algorithm, options); + this._handle = new _Sign(); + this._handle.init(algorithm); + + Writable.call(this, options); +} + +inherits(Sign, Writable); + +Sign.prototype._write = function _write(chunk, encoding, callback) { + this._handle.update(chunk, encoding); + callback(); +}; + +Sign.prototype.update = function update(data, encoding) { + encoding = encoding || getDefaultEncoding(); + this._handle.update(data, encoding); + return this; +}; + +Sign.prototype.sign = function sign(options, encoding) { + if (!options) + throw new errors.Error('ERR_CRYPTO_SIGN_KEY_REQUIRED'); + + var key = options.key || options; + var passphrase = options.passphrase || null; + + // Options specific to RSA + var rsaPadding = RSA_PKCS1_PADDING; + if (options.hasOwnProperty('padding')) { + if (options.padding === options.padding >> 0) { + rsaPadding = options.padding; + } else { + throw new errors.TypeError('ERR_INVALID_OPT_VALUE', + 'padding', + options.padding); + } + } + + var pssSaltLength = RSA_PSS_SALTLEN_AUTO; + if (options.hasOwnProperty('saltLength')) { + if (options.saltLength === options.saltLength >> 0) { + pssSaltLength = options.saltLength; + } else { + throw new errors.TypeError('ERR_INVALID_OPT_VALUE', + 'saltLength', + options.saltLength); + } + } + + var ret = this._handle.sign(toBuf(key), passphrase, rsaPadding, + pssSaltLength); + + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + ret = ret.toString(encoding); + + return ret; +}; + + +function Verify(algorithm, options) { + if (!(this instanceof Verify)) + return new Verify(algorithm, options); + + this._handle = new _Verify(); + this._handle.init(algorithm); + + Writable.call(this, options); +} + +inherits(Verify, Writable); + +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; + sigEncoding = sigEncoding || getDefaultEncoding(); + + // Options specific to RSA + var rsaPadding = RSA_PKCS1_PADDING; + if (options.hasOwnProperty('padding')) { + if (options.padding === options.padding >> 0) { + rsaPadding = options.padding; + } else { + throw new errors.TypeError('ERR_INVALID_OPT_VALUE', + 'padding', + options.padding); + } + } + + var pssSaltLength = RSA_PSS_SALTLEN_AUTO; + if (options.hasOwnProperty('saltLength')) { + if (options.saltLength === options.saltLength >> 0) { + pssSaltLength = options.saltLength; + } else { + throw new errors.TypeError('ERR_INVALID_OPT_VALUE', + 'saltLength', + options.saltLength); + } + } + + return this._handle.verify(toBuf(key), toBuf(signature, sigEncoding), + rsaPadding, pssSaltLength); +}; + +module.exports = { + Sign, + Verify +}; diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js new file mode 100644 index 0000000000..9e242dc917 --- /dev/null +++ b/lib/internal/crypto/util.js @@ -0,0 +1,70 @@ +'use strict'; + +const { + getCiphers: _getCiphers, + getCurves: _getCurves, + getHashes: _getHashes, + setEngine: _setEngine +} = process.binding('crypto'); + +const { + ENGINE_METHOD_ALL +} = process.binding('constants').crypto; + +const errors = require('internal/errors'); +const { Buffer } = require('buffer'); +const { + cachedResult, + filterDuplicateStrings +} = require('internal/util'); + +var defaultEncoding = 'buffer'; + +function setDefaultEncoding(val) { + defaultEncoding = val; +} + +function getDefaultEncoding() { + return defaultEncoding; +} + +// This is here because many functions accepted binary strings without +// any explicit encoding in older versions of node, and we don't want +// to break them unnecessarily. +function toBuf(str, encoding) { + if (typeof str === 'string') { + if (encoding === 'buffer' || !encoding) + encoding = 'utf8'; + return Buffer.from(str, encoding); + } + return str; +} + +const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers())); +const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes())); +const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves())); + +function setEngine(id, flags) { + if (typeof id !== 'string') + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'id', 'string'); + + if (flags && typeof flags !== 'number') + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'flags', 'number'); + flags = flags >>> 0; + + // Use provided engine for everything by default + if (flags === 0) + flags = ENGINE_METHOD_ALL; + + return _setEngine(id, flags); +} + +module.exports = { + getCiphers, + getCurves, + getDefaultEncoding, + getHashes, + setDefaultEncoding, + setEngine, + toBuf +}; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 7f72e7a0e5..261378e2b1 100755 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -125,6 +125,11 @@ E('ERR_CHILD_CLOSED_BEFORE_REPLY', 'Child closed before reply received'); E('ERR_CONSOLE_WRITABLE_STREAM', 'Console expects a writable stream instance for %s'); E('ERR_CPU_USAGE', 'Unable to obtain cpu usage %s'); +E('ERR_CRYPTO_ECDH_INVALID_FORMAT', 'Invalid ECDH format: %s'); +E('ERR_CRYPTO_HASH_DIGEST_NO_UTF16', 'hash.digest() does not support UTF-16'); +E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called'); +E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed'); +E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign'); E('ERR_DNS_SET_SERVERS_FAILED', (err, servers) => `c-ares failed to set servers: "${err}" [${servers}]`); E('ERR_ENCODING_INVALID_ENCODED_DATA', @@ -253,6 +258,7 @@ E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object'); E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support'); E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU'); E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported'); +E('ERR_OUT_OF_RANGE', 'The "%s" argument is out of range'); E('ERR_OUTOFMEMORY', 'Out of memory'); E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s'); E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s'); diff --git a/node.gyp b/node.gyp index c06008a39a..39106ce346 100644 --- a/node.gyp +++ b/node.gyp @@ -84,6 +84,14 @@ 'lib/internal/cluster/shared_handle.js', 'lib/internal/cluster/utils.js', 'lib/internal/cluster/worker.js', + 'lib/internal/crypto/certificate.js', + 'lib/internal/crypto/cipher.js', + 'lib/internal/crypto/diffiehellman.js', + 'lib/internal/crypto/hash.js', + 'lib/internal/crypto/pbkdf2.js', + 'lib/internal/crypto/random.js', + 'lib/internal/crypto/sig.js', + 'lib/internal/crypto/util.js', 'lib/internal/encoding.js', 'lib/internal/errors.js', 'lib/internal/freelist.js', diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 174f502633..d5038b7ab5 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -3699,7 +3699,6 @@ void Hmac::New(const FunctionCallbackInfo& args) { void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) { HandleScope scope(env()->isolate()); - CHECK_EQ(initialised_, false); const EVP_MD* md = EVP_get_digestbyname(hash_type); if (md == nullptr) { return env()->ThrowError("Unknown message digest"); @@ -3720,13 +3719,6 @@ void Hmac::HmacInit(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder()); Environment* env = hmac->env(); - if (args.Length() < 2) { - return env->ThrowError("Hash type and key arguments are mandatory"); - } - - THROW_AND_RETURN_IF_NOT_STRING(args[0], "Hash type"); - THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key"); - const node::Utf8Value hash_type(env->isolate(), args[0]); const char* buffer_data = Buffer::Data(args[1]); size_t buffer_length = Buffer::Length(args[1]); @@ -3748,24 +3740,22 @@ void Hmac::HmacUpdate(const FunctionCallbackInfo& args) { Hmac* hmac; ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder()); - THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data"); - // Only copy the data if we have to, because it's a string - bool r; + bool r = true; if (args[0]->IsString()) { StringBytes::InlineDecoder decoder; - if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) { + args.GetReturnValue().Set(false); return; + } r = hmac->HmacUpdate(decoder.out(), decoder.size()); - } else { + } else if (args[0]->IsArrayBufferView()) { char* buf = Buffer::Data(args[0]); size_t buflen = Buffer::Length(args[0]); r = hmac->HmacUpdate(buf, buflen); } - if (!r) { - return env->ThrowTypeError("HmacUpdate fail"); - } + args.GetReturnValue().Set(r); } @@ -3777,13 +3767,9 @@ void Hmac::HmacDigest(const FunctionCallbackInfo& args) { enum encoding encoding = BUFFER; if (args.Length() >= 1) { - CHECK(args[0]->IsString()); encoding = ParseEncoding(env->isolate(), args[0], BUFFER); } - - if (encoding == UCS2) { - return env->ThrowError("hmac.digest() does not support UTF-16"); - } + CHECK_NE(encoding, UCS2); // Digest does not support UTF-16 unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len = 0; @@ -3825,10 +3811,6 @@ void Hash::Initialize(Environment* env, v8::Local target) { void Hash::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() == 0 || !args[0]->IsString()) { - return env->ThrowError("Must give hashtype string as argument"); - } - const node::Utf8Value hash_type(env->isolate(), args[0]); Hash* hash = new Hash(env, args.This()); @@ -3840,7 +3822,6 @@ void Hash::New(const FunctionCallbackInfo& args) { bool Hash::HashInit(const char* hash_type) { - CHECK_EQ(initialised_, false); const EVP_MD* md = EVP_get_digestbyname(hash_type); if (md == nullptr) return false; @@ -3868,31 +3849,22 @@ void Hash::HashUpdate(const FunctionCallbackInfo& args) { Hash* hash; ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder()); - THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data"); - - if (!hash->initialised_) { - return env->ThrowError("Not initialized"); - } - if (hash->finalized_) { - return env->ThrowError("Digest already called"); - } - // Only copy the data if we have to, because it's a string - bool r; + bool r = true; if (args[0]->IsString()) { StringBytes::InlineDecoder decoder; - if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) { + args.GetReturnValue().Set(false); return; + } r = hash->HashUpdate(decoder.out(), decoder.size()); - } else { + } else if (args[0]->IsArrayBufferView()) { char* buf = Buffer::Data(args[0]); size_t buflen = Buffer::Length(args[0]); r = hash->HashUpdate(buf, buflen); } - if (!r) { - return env->ThrowTypeError("HashUpdate fail"); - } + args.GetReturnValue().Set(r); } @@ -3902,23 +3874,11 @@ void Hash::HashDigest(const FunctionCallbackInfo& args) { Hash* hash; ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder()); - if (!hash->initialised_) { - return env->ThrowError("Not initialized"); - } - if (hash->finalized_) { - return env->ThrowError("Digest already called"); - } - enum encoding encoding = BUFFER; if (args.Length() >= 1) { - CHECK(args[0]->IsString()); encoding = ParseEncoding(env->isolate(), args[0], BUFFER); } - if (encoding == UCS2) { - return env->ThrowError("hash.digest() does not support UTF-16"); - } - unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len; @@ -5562,13 +5522,14 @@ void RandomBytesCheck(RandomBytesRequest* req, Local (*argv)[2]) { req->object()->Get(req->env()->context(), req->env()->buffer_string()).ToLocalChecked(); - if (buffer->IsUint8Array()) { + if (buffer->IsArrayBufferView()) { CHECK_LE(req->size(), Buffer::Length(buffer)); char* buf = Buffer::Data(buffer); memcpy(buf, data, req->size()); (*argv)[1] = buffer; } else { - (*argv)[1] = Buffer::New(req->env(), data, size).ToLocalChecked(); + (*argv)[1] = Buffer::New(req->env(), data, size) + .ToLocalChecked(); } } } @@ -5649,7 +5610,7 @@ void RandomBytes(const FunctionCallbackInfo& args) { void RandomBytesBuffer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - CHECK(args[0]->IsUint8Array()); + CHECK(args[0]->IsArrayBufferView()); CHECK(args[1]->IsUint32()); CHECK(args[2]->IsUint32()); diff --git a/src/node_util.cc b/src/node_util.cc index ab1f3c9f91..cf26eca692 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -19,6 +19,7 @@ using v8::Value; #define VALUE_METHOD_MAP(V) \ V(isArrayBuffer, IsArrayBuffer) \ + V(isArrayBufferView, IsArrayBufferView) \ V(isAsyncFunction, IsAsyncFunction) \ V(isDataView, IsDataView) \ V(isDate, IsDate) \ diff --git a/test/parallel/test-crypto-binary-default.js b/test/parallel/test-crypto-binary-default.js index 0350015dc6..dbf814ff4b 100644 --- a/test/parallel/test-crypto-binary-default.js +++ b/test/parallel/test-crypto-binary-default.js @@ -568,9 +568,12 @@ testCipher4(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); // update() should only take buffers / strings -assert.throws(function() { - crypto.createHash('sha1').update({ foo: 'bar' }); -}, /^TypeError: Data must be a string or a buffer$/); +common.expectsError( + () => crypto.createHash('sha1').update({ foo: 'bar' }), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + }); // Test Diffie-Hellman with two parties sharing a secret, diff --git a/test/parallel/test-crypto-certificate.js b/test/parallel/test-crypto-certificate.js index 5af62b58bf..b639185a55 100644 --- a/test/parallel/test-crypto-certificate.js +++ b/test/parallel/test-crypto-certificate.js @@ -26,6 +26,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { Certificate } = crypto; const fixtures = require('../common/fixtures'); crypto.DEFAULT_ENCODING = 'buffer'; @@ -35,26 +36,47 @@ const spkacValid = fixtures.readSync('spkac.valid'); const spkacFail = fixtures.readSync('spkac.fail'); const spkacPem = fixtures.readSync('spkac.pem'); -const certificate = new crypto.Certificate(); +{ + // Test instance methods + const certificate = new Certificate(); -assert.strictEqual(certificate.verifySpkac(spkacValid), true); -assert.strictEqual(certificate.verifySpkac(spkacFail), false); + assert.strictEqual(certificate.verifySpkac(spkacValid), true); + assert.strictEqual(certificate.verifySpkac(spkacFail), false); -assert.strictEqual( - stripLineEndings(certificate.exportPublicKey(spkacValid).toString('utf8')), - stripLineEndings(spkacPem.toString('utf8')) -); -assert.strictEqual(certificate.exportPublicKey(spkacFail), ''); + assert.strictEqual( + stripLineEndings(certificate.exportPublicKey(spkacValid).toString('utf8')), + stripLineEndings(spkacPem.toString('utf8')) + ); + assert.strictEqual(certificate.exportPublicKey(spkacFail), ''); -assert.strictEqual( - certificate.exportChallenge(spkacValid).toString('utf8'), - 'fb9ab814-6677-42a4-a60c-f905d1a6924d' -); -assert.strictEqual(certificate.exportChallenge(spkacFail), ''); + assert.strictEqual( + certificate.exportChallenge(spkacValid).toString('utf8'), + 'fb9ab814-6677-42a4-a60c-f905d1a6924d' + ); + assert.strictEqual(certificate.exportChallenge(spkacFail), ''); +} + +{ + // Test static methods + assert.strictEqual(Certificate.verifySpkac(spkacValid), true); + assert.strictEqual(Certificate.verifySpkac(spkacFail), false); + + assert.strictEqual( + stripLineEndings(Certificate.exportPublicKey(spkacValid).toString('utf8')), + stripLineEndings(spkacPem.toString('utf8')) + ); + assert.strictEqual(Certificate.exportPublicKey(spkacFail), ''); + + assert.strictEqual( + Certificate.exportChallenge(spkacValid).toString('utf8'), + 'fb9ab814-6677-42a4-a60c-f905d1a6924d' + ); + assert.strictEqual(Certificate.exportChallenge(spkacFail), ''); +} function stripLineEndings(obj) { return obj.replace(/\n/g, ''); } // direct call Certificate() should return instance -assert(crypto.Certificate() instanceof crypto.Certificate); +assert(Certificate() instanceof Certificate); diff --git a/test/parallel/test-crypto-dh.js b/test/parallel/test-crypto-dh.js index 3b51dc5590..62d0f7d6b0 100644 --- a/test/parallel/test-crypto-dh.js +++ b/test/parallel/test-crypto-dh.js @@ -22,24 +22,22 @@ assert.strictEqual(secret2.toString('base64'), secret1); assert.strictEqual(dh1.verifyError, 0); assert.strictEqual(dh2.verifyError, 0); -const argumentsError = - /^TypeError: First argument should be number, string, Buffer, TypedArray, or DataView$/; - -assert.throws(() => { - crypto.createDiffieHellman([0x1, 0x2]); -}, argumentsError); - -assert.throws(() => { - crypto.createDiffieHellman(() => { }); -}, argumentsError); - -assert.throws(() => { - crypto.createDiffieHellman(/abc/); -}, argumentsError); - -assert.throws(() => { - crypto.createDiffieHellman({}); -}, argumentsError); +[ + [0x1, 0x2], + () => { }, + /abc/, + {} +].forEach((i) => { + common.expectsError( + () => crypto.createDiffieHellman(i), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "sizeOrKey" argument must be one of type number, string, ' + + 'Buffer, TypedArray, or DataView' + } + ); +}); // Create "another dh1" using generated keys from dh1, // and compute secret again @@ -198,9 +196,14 @@ if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) { firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0]; assert(firstByte === 6 || firstByte === 7); // format value should be string - assert.throws(() => { - ecdh1.getPublicKey('buffer', 10); - }, /^TypeError: Bad format: 10$/); + + common.expectsError( + () => ecdh1.getPublicKey('buffer', 10), + { + code: 'ERR_CRYPTO_ECDH_INVALID_FORMAT', + type: TypeError, + message: 'Invalid ECDH format: 10' + }); // ECDH should check that point is on curve const ecdh3 = crypto.createECDH('secp256k1'); @@ -331,6 +334,10 @@ if (availableCurves.has('prime256v1') && availableHashes.has('sha256')) { } // invalid test: curve argument is undefined -assert.throws(() => { - crypto.createECDH(); -}, /^TypeError: "curve" argument should be a string$/); +common.expectsError( + () => crypto.createECDH(), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "curve" argument must be of type string' + }); diff --git a/test/parallel/test-crypto-engine.js b/test/parallel/test-crypto-engine.js index b2fe154d3c..b731ec2f03 100644 --- a/test/parallel/test-crypto-engine.js +++ b/test/parallel/test-crypto-engine.js @@ -4,13 +4,20 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); const crypto = require('crypto'); -assert.throws(function() { - crypto.setEngine(true); -}, /^TypeError: "id" argument should be a string$/); +common.expectsError( + () => crypto.setEngine(true), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "id" argument must be of type string' + }); -assert.throws(function() { - crypto.setEngine('/path/to/engine', 'notANumber'); -}, /^TypeError: "flags" argument should be a number, if present$/); +common.expectsError( + () => crypto.setEngine('/path/to/engine', 'notANumber'), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "flags" argument must be of type number' + }); diff --git a/test/parallel/test-crypto-hash.js b/test/parallel/test-crypto-hash.js index 83b355a2be..82a76131fb 100644 --- a/test/parallel/test-crypto-hash.js +++ b/test/parallel/test-crypto-hash.js @@ -105,14 +105,34 @@ assert.notStrictEqual( const h3 = crypto.createHash('sha256'); h3.digest(); -assert.throws(function() { - h3.digest(); -}, /Digest already called/); -assert.throws(function() { - h3.update('foo'); -}, /Digest already called/); +common.expectsError( + () => h3.digest(), + { + code: 'ERR_CRYPTO_HASH_FINALIZED', + type: Error + }); + +common.expectsError( + () => h3.update('foo'), + { + code: 'ERR_CRYPTO_HASH_FINALIZED', + type: Error + }); + +common.expectsError( + () => crypto.createHash('sha256').digest('ucs2'), + { + code: 'ERR_CRYPTO_HASH_DIGEST_NO_UTF16', + type: Error + } +); -assert.throws(function() { - crypto.createHash('sha256').digest('ucs2'); -}, /^Error: hash\.digest\(\) does not support UTF-16$/); +common.expectsError( + () => crypto.createHash(), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "algorithm" argument must be of type string' + } +); diff --git a/test/parallel/test-crypto-hmac.js b/test/parallel/test-crypto-hmac.js index 6ddffbf56f..a2921dae47 100644 --- a/test/parallel/test-crypto-hmac.js +++ b/test/parallel/test-crypto-hmac.js @@ -6,19 +6,11 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -// Test for binding layer robustness -{ - const binding = process.binding('crypto'); - const h = new binding.Hmac(); - // Fail to init the Hmac with an algorithm. - assert.throws(() => h.update('hello'), /^TypeError: HmacUpdate fail$/); -} - // Test HMAC const h1 = crypto.createHmac('sha1', 'Node') - .update('some data') - .update('to hmac') - .digest('hex'); + .update('some data') + .update('to hmac') + .digest('hex'); assert.strictEqual(h1, '19fd6e1ba73d9ed2224dd5094a71babe85d9a892', 'test HMAC'); // Test HMAC (Wikipedia Test Cases) @@ -376,9 +368,12 @@ for (let i = 0, l = rfc2202_sha1.length; i < l; i++) { ); } -assert.throws(function() { - crypto.createHmac('sha256', 'w00t').digest('ucs2'); -}, /^Error: hmac\.digest\(\) does not support UTF-16$/); +common.expectsError( + () => crypto.createHmac('sha256', 'w00t').digest('ucs2'), + { + code: 'ERR_CRYPTO_HASH_DIGEST_NO_UTF16', + type: Error + }); // Check initialized -> uninitialized state transition after calling digest(). { diff --git a/test/parallel/test-crypto-pbkdf2.js b/test/parallel/test-crypto-pbkdf2.js index f8f4286525..c495b9306f 100644 --- a/test/parallel/test-crypto-pbkdf2.js +++ b/test/parallel/test-crypto-pbkdf2.js @@ -55,9 +55,13 @@ function ondone(err, key) { } // Error path should not leak memory (check with valgrind). -assert.throws(function() { - crypto.pbkdf2('password', 'salt', 1, 20, null); -}, /^Error: No callback provided to pbkdf2$/); +common.expectsError( + () => crypto.pbkdf2('password', 'salt', 1, 20, null), + { + code: 'ERR_INVALID_CALLBACK', + type: TypeError + } +); // Should not work with Infinity key length assert.throws(function() { @@ -95,10 +99,18 @@ assert.doesNotThrow(() => { })); }); -assert.throws(() => { - crypto.pbkdf2('password', 'salt', 8, 8, common.mustNotCall()); -}, /^TypeError: The "digest" argument is required and must not be undefined$/); +common.expectsError( + () => crypto.pbkdf2('password', 'salt', 8, 8, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "digest" argument must be one of type string or null' + }); -assert.throws(() => { - crypto.pbkdf2Sync('password', 'salt', 8, 8); -}, /^TypeError: The "digest" argument is required and must not be undefined$/); +common.expectsError( + () => crypto.pbkdf2Sync('password', 'salt', 8, 8), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "digest" argument must be one of type string or null' + }); diff --git a/test/parallel/test-crypto-random.js b/test/parallel/test-crypto-random.js index fb80e313ab..f5dad9d62a 100644 --- a/test/parallel/test-crypto-random.js +++ b/test/parallel/test-crypto-random.js @@ -33,15 +33,6 @@ crypto.DEFAULT_ENCODING = 'buffer'; // bump, we register a lot of exit listeners process.setMaxListeners(256); -const errMessages = { - offsetNotNumber: /^TypeError: offset must be a number$/, - offsetOutOfRange: /^RangeError: offset out of range$/, - offsetNotUInt32: /^TypeError: offset must be a uint32$/, - sizeNotNumber: /^TypeError: size must be a number$/, - sizeNotUInt32: /^TypeError: size must be a uint32$/, - bufferTooSmall: /^RangeError: buffer too small$/, -}; - const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/; [crypto.randomBytes, crypto.pseudoRandomBytes].forEach(function(f) { [-1, undefined, null, false, true, {}, []].forEach(function(value) { @@ -74,6 +65,46 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/; assert.notStrictEqual(before, after); } +{ + const buf = new Uint16Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new Uint32Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new Float32Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new Float64Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new DataView(new ArrayBuffer(10)); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); +} + { const buf = Buffer.alloc(10); const before = buf.toString('hex'); @@ -94,6 +125,56 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/; })); } +{ + const buf = new Uint16Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new Uint32Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new Float32Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new Float64Array(10); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new DataView(new ArrayBuffer(10)); + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + { const buf = Buffer.alloc(10); const before = buf.toString('hex'); @@ -155,108 +236,228 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/; const len = Buffer.byteLength(buf); assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`); - assert.throws(() => { - crypto.randomFillSync(buf, 'test'); - }, errMessages.offsetNotNumber); - - assert.throws(() => { - crypto.randomFillSync(buf, NaN); - }, errMessages.offsetNotNumber); - - assert.throws(() => { - crypto.randomFill(buf, 'test', common.mustNotCall()); - }, errMessages.offsetNotNumber); - - assert.throws(() => { - crypto.randomFill(buf, NaN, common.mustNotCall()); - }, errMessages.offsetNotNumber); - - assert.throws(() => { - crypto.randomFillSync(buf, 11); - }, errMessages.offsetOutOfRange); - - assert.throws(() => { - crypto.randomFillSync(buf, max); - }, errMessages.offsetOutOfRange); - - assert.throws(() => { - crypto.randomFill(buf, 11, common.mustNotCall()); - }, errMessages.offsetOutOfRange); - - assert.throws(() => { - crypto.randomFill(buf, max, common.mustNotCall()); - }, errMessages.offsetOutOfRange); - - assert.throws(() => { - crypto.randomFillSync(buf, 0, 'test'); - }, errMessages.sizeNotNumber); - - assert.throws(() => { - crypto.randomFillSync(buf, 0, NaN); - }, errMessages.sizeNotNumber); - - assert.throws(() => { - crypto.randomFill(buf, 0, 'test', common.mustNotCall()); - }, errMessages.sizeNotNumber); - - assert.throws(() => { - crypto.randomFill(buf, 0, NaN, common.mustNotCall()); - }, errMessages.sizeNotNumber); + common.expectsError( + () => crypto.randomFillSync(buf, 'test'), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, NaN), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 'test', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, NaN, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, 11), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "offset" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, max), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "offset" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 11, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "offset" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, max, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "offset" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, 0, 'test'), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, 0, NaN), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 0, 'test', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type number' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 0, NaN, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type number' + } + ); { const size = (-1 >>> 0) + 1; - assert.throws(() => { - crypto.randomFillSync(buf, 0, -10); - }, errMessages.sizeNotUInt32); - - assert.throws(() => { - crypto.randomFillSync(buf, 0, size); - }, errMessages.sizeNotUInt32); - - assert.throws(() => { - crypto.randomFill(buf, 0, -10, common.mustNotCall()); - }, errMessages.sizeNotUInt32); - - assert.throws(() => { - crypto.randomFill(buf, 0, size, common.mustNotCall()); - }, errMessages.sizeNotUInt32); + common.expectsError( + () => crypto.randomFillSync(buf, 0, -10), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type uint32' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, 0, size), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type uint32' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 0, -10, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type uint32' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 0, size, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "size" argument must be of type uint32' + } + ); } - assert.throws(() => { - crypto.randomFillSync(buf, -10); - }, errMessages.offsetNotUInt32); - - assert.throws(() => { - crypto.randomFill(buf, -10, common.mustNotCall()); - }, errMessages.offsetNotUInt32); - - assert.throws(() => { - crypto.randomFillSync(buf, 1, 10); - }, errMessages.bufferTooSmall); - - assert.throws(() => { - crypto.randomFill(buf, 1, 10, common.mustNotCall()); - }, errMessages.bufferTooSmall); - - assert.throws(() => { - crypto.randomFillSync(buf, 0, 12); - }, errMessages.bufferTooSmall); - - assert.throws(() => { - crypto.randomFill(buf, 0, 12, common.mustNotCall()); - }, errMessages.bufferTooSmall); + common.expectsError( + () => crypto.randomFillSync(buf, -10), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type uint32' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, -10, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type uint32' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, 1, 10), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "size" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 1, 10, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "size" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFillSync(buf, 0, 12), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "size" argument is out of range' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, 0, 12, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + type: RangeError, + message: 'The "size" argument is out of range' + } + ); { // Offset is too big const offset = (-1 >>> 0) + 1; - assert.throws(() => { - crypto.randomFillSync(buf, offset, 10); - }, errMessages.offsetNotUInt32); - - assert.throws(() => { - crypto.randomFill(buf, offset, 10, common.mustNotCall()); - }, errMessages.offsetNotUInt32); + common.expectsError( + () => crypto.randomFillSync(buf, offset, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type uint32' + } + ); + + common.expectsError( + () => crypto.randomFill(buf, offset, 10, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "offset" argument must be of type uint32' + } + ); } } } @@ -265,4 +466,21 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/; // length exceeds max acceptable value" assert.throws(function() { crypto.randomBytes((-1 >>> 0) + 1); -}, errMessages.sizeNotUInt32); +}, /^TypeError: size must be a uint32$/); + +[1, true, NaN, null, undefined, {}, []].forEach((i) => { + common.expectsError( + () => crypto.randomFillSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + } + ); + common.expectsError( + () => crypto.randomFill(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + } + ); +}); diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index 284261cd9e..bdbca3dbb8 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -197,21 +197,21 @@ const modSize = 1024; // Test exceptions for invalid `padding` and `saltLength` values { - const paddingNotInteger = /^TypeError: padding must be an integer$/; - const saltLengthNotInteger = /^TypeError: saltLength must be an integer$/; - [null, undefined, NaN, 'boom', {}, [], true, false] .forEach((invalidValue) => { - assert.throws(() => { + common.expectsError(() => { crypto.createSign('SHA256') .update('Test123') .sign({ key: keyPem, padding: invalidValue }); - }, paddingNotInteger); + }, { + code: 'ERR_INVALID_OPT_VALUE', + type: TypeError + }); - assert.throws(() => { + common.expectsError(() => { crypto.createSign('SHA256') .update('Test123') .sign({ @@ -219,7 +219,10 @@ const modSize = 1024; padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: invalidValue }); - }, saltLengthNotInteger); + }, { + code: 'ERR_INVALID_OPT_VALUE', + type: TypeError + }); }); assert.throws(() => { @@ -234,9 +237,12 @@ const modSize = 1024; // Test throws exception when key options is null { - assert.throws(() => { + common.expectsError(() => { crypto.createSign('SHA1').update('Test123').sign(null, 'base64'); - }, /^Error: No key provided to sign$/); + }, { + code: 'ERR_CRYPTO_SIGN_KEY_REQUIRED', + type: Error + }); } // RSA-PSS Sign test by verifying with 'openssl dgst -verify' diff --git a/test/parallel/test-crypto.js b/test/parallel/test-crypto.js index 6262e08384..2a704ff07c 100644 --- a/test/parallel/test-crypto.js +++ b/test/parallel/test-crypto.js @@ -67,9 +67,12 @@ assert.throws(function() { // update() should only take buffers / strings -assert.throws(function() { - crypto.createHash('sha1').update({ foo: 'bar' }); -}, /^TypeError: Data must be a string or a buffer$/); +common.expectsError( + () => crypto.createHash('sha1').update({ foo: 'bar' }), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + }); function validateList(list) {