diff options
author | James M Snell <jasnell@gmail.com> | 2017-09-06 08:10:34 -0700 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2017-09-18 08:10:59 -0700 |
commit | c75f87cc4c8d3699e081d37bb5bf47a70d830fdb (patch) | |
tree | 9d79319f568ff43e36e05a8d2634130adfacfb74 /lib | |
parent | 8fa5fcc0ba74c23490c34da1a6c6e9a454280740 (diff) | |
download | android-node-v8-c75f87cc4c8d3699e081d37bb5bf47a70d830fdb.tar.gz android-node-v8-c75f87cc4c8d3699e081d37bb5bf47a70d830fdb.tar.bz2 android-node-v8-c75f87cc4c8d3699e081d37bb5bf47a70d830fdb.zip |
crypto: refactor the crypto module
* Split single monolithic file into multiple
* Make Certificate methods static
* Allow randomFill(Sync) to use any ArrayBufferView
* Use internal/errors throughout
* Improve arg validation in Hash/Hmac
* Doc updates
PR-URL: https://github.com/nodejs/node/pull/15231
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/crypto.js | 906 | ||||
-rw-r--r-- | lib/internal/crypto/certificate.js | 40 | ||||
-rw-r--r-- | lib/internal/crypto/cipher.js | 214 | ||||
-rw-r--r-- | lib/internal/crypto/diffiehellman.js | 216 | ||||
-rw-r--r-- | lib/internal/crypto/hash.js | 125 | ||||
-rw-r--r-- | lib/internal/crypto/pbkdf2.js | 59 | ||||
-rw-r--r-- | lib/internal/crypto/random.js | 98 | ||||
-rw-r--r-- | lib/internal/crypto/sig.js | 131 | ||||
-rw-r--r-- | lib/internal/crypto/util.js | 70 | ||||
-rwxr-xr-x | lib/internal/errors.js | 6 |
10 files changed, 1091 insertions, 774 deletions
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'); |