From 846fe06bc184261738e8b43b354fe63816432d5f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 2 Feb 2017 21:58:20 -0800 Subject: [PATCH] crypto: refactor crypto module Refactor the crypto module to make the code more readable and maintainable. Leverages ES6 classes. No test changes are required. Existing code should keep working. Provide improved inspect support for crypto classes. Remove dead code in ECDH.prototype.getPublicKey with an unused arg type check. --- lib/crypto.js | 715 +++++---------------------- lib/internal/crypto/certificate.js | 25 + lib/internal/crypto/cipher.js | 170 +++++++ lib/internal/crypto/diffiehellman.js | 207 ++++++++ lib/internal/crypto/hash.js | 79 +++ lib/internal/crypto/sign.js | 104 ++++ lib/internal/crypto/util.js | 18 + node.gyp | 6 + test/parallel/test-crypto-dh.js | 2 +- 9 files changed, 721 insertions(+), 605 deletions(-) create mode 100644 lib/internal/crypto/certificate.js create mode 100644 lib/internal/crypto/cipher.js create mode 100644 lib/internal/crypto/diffiehellman.js create mode 100644 lib/internal/crypto/hash.js create mode 100644 lib/internal/crypto/sign.js create mode 100644 lib/internal/crypto/util.js diff --git a/lib/crypto.js b/lib/crypto.js index da381463fd4d..24a4e09fa03e 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -6,323 +6,27 @@ const internalUtil = require('internal/util'); internalUtil.assertCrypto(); -exports.DEFAULT_ENCODING = 'buffer'; - -const constants = process.binding('constants').crypto; const binding = process.binding('crypto'); -const randomBytes = binding.randomBytes; +const constants = process.binding('constants').crypto; +const cryptoUtil = require('internal/crypto/util'); +const internalCert = require('internal/crypto/certificate'); +const internalCipher = require('internal/crypto/cipher'); +const internalDh = require('internal/crypto/diffiehellman'); +const internalHash = require('internal/crypto/hash'); +const internalSign = require('internal/crypto/sign'); const getCiphers = binding.getCiphers; const getHashes = binding.getHashes; const getCurves = binding.getCurves; const getFipsCrypto = binding.getFipsCrypto; +const randomBytes = binding.randomBytes; const setFipsCrypto = binding.setFipsCrypto; const timingSafeEqual = binding.timingSafeEqual; -const Buffer = require('buffer').Buffer; -const stream = require('stream'); -const util = require('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; - 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; - var ret = this._handle.sign(toBuf(key), null, passphrase); - - 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(object, signature, sigEncoding) { - sigEncoding = sigEncoding || exports.DEFAULT_ENCODING; - return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding)); -}; - -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); - }; -} +const toBuf = cryptoUtil.toBuf; +const filterDuplicateStrings = internalUtil.filterDuplicateStrings; +const cachedResult = internalUtil.cachedResult; -function rsaPrivate(method, defaultPadding) { +function rsa(method, defaultPadding) { return function(options, buffer) { var key = options.key || options; var passphrase = options.passphrase || null; @@ -331,218 +35,106 @@ function rsaPrivate(method, defaultPadding) { }; } -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 (!(sizeOrKey instanceof Buffer) && - typeof sizeOrKey !== 'number' && - typeof sizeOrKey !== 'string') - throw new TypeError('First argument should be number, string or Buffer'); - - if (keyEncoding) { - if (typeof keyEncoding !== 'string' || - (!Buffer.isEncoding(keyEncoding) && keyEncoding !== 'buffer')) { - genEncoding = generator; - generator = keyEncoding; - keyEncoding = false; +module.exports = exports = { + createCipher: internalCipher.createCipher, + createCipheriv: internalCipher.createCipheriv, + createDecipher: internalCipher.createDecipher, + createDecipheriv: internalCipher.createDecipheriv, + createDiffieHellman: internalDh.createDiffieHellman, + createDiffieHellmanGroup: internalDh.createDiffieHellmanGroup, + createECDH: internalDh.createECDH, + createHash: internalHash.createHash, + createHmac: internalHash.createHmac, + createSign: internalSign.createSign, + createVerify: internalSign.createVerify, + getDiffieHellman: internalDh.createDiffieHellmanGroup, + getCiphers: cachedResult(() => filterDuplicateStrings(getCiphers())), + getCurves: cachedResult(() => filterDuplicateStrings(getCurves())), + getHashes: cachedResult(() => filterDuplicateStrings(getHashes())), + pbkdf2: pbkdf2, + pbkdf2Sync: pbkdf2Sync, + publicEncrypt: rsa(binding.publicEncrypt, constants.RSA_PKCS1_OAEP_PADDING), + publicDecrypt: rsa(binding.publicDecrypt, constants.RSA_PKCS1_PADDING), + privateEncrypt: rsa(binding.privateEncrypt, constants.RSA_PKCS1_PADDING), + privateDecrypt: rsa(binding.privateDecrypt, constants.RSA_PKCS1_OAEP_PADDING), + prng: randomBytes, + pseudoRandomBytes: randomBytes, + randomBytes: randomBytes, + rng: randomBytes, + setEngine: setEngine, + timingSafeEqual: timingSafeEqual, + Certificate: internalCert.createCertificate, + Cipher: internalCipher.createCipher, + Cipheriv: internalCipher.createCipheriv, + Decipher: internalCipher.createDecipher, + Decipheriv: internalCipher.createDecipheriv, + DiffieHellman: internalDh.createDiffieHellman, + DiffieHellmanGroup: internalDh.createDiffieHellmanGroup, + Hash: internalHash.createHash, + Hmac: internalHash.createHmac, + Sign: internalSign.createSign, + Verify: internalSign.createVerify, + + _toBuf: toBuf +}; + +Object.defineProperties(exports, { + 'constants': { + configurable: false, + enumerable: true, + value: constants + }, + 'DEFAULT_ENCODING': { + configurable: true, + enumerable: true, + get: function() { + return cryptoUtil.DEFAULT_ENCODING; + }, + set: function(encoding) { + encoding = internalUtil.normalizeEncoding(encoding); + cryptoUtil.DEFAULT_ENCODING = encoding; } - } - - 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', { + }, + 'fips': { + get: getFipsCrypto, + set: setFipsCrypto + }, + + // Legacy API + 'createCredentials': { + configurable: true, 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', { + get: internalUtil.deprecate(function() { + return require('tls').createSecureContext; + }, 'crypto.createCredentials is deprecated. ' + + 'Use tls.createSecureContext instead.', 'DEP0010') + }, + 'Credentials': { + configurable: true, 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; -} - - -DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) { - encoding = encoding || exports.DEFAULT_ENCODING; - this._handle.setPublicKey(toBuf(key, encoding)); - return this; -}; - + get: internalUtil.deprecate(function() { + return require('tls').SecureContext; + }, 'crypto.Credentials is deprecated. ' + + 'Use tls.SecureContext instead.', 'DEP0011') + } +}); -DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { - encoding = encoding || exports.DEFAULT_ENCODING; - this._handle.setPrivateKey(toBuf(key, encoding)); - return this; -}; +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; -function ECDH(curve) { - if (typeof curve !== 'string') - throw new TypeError('"curve" argument should be a string'); + // Use provided engine for everything by default + if (flags === 0) + flags = constants.ENGINE_METHOD_ALL; - this._handle = new binding.ECDH(curve); + return binding.setEngine(id, flags); } -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) { +function pbkdf2(password, salt, iterations, keylen, digest, callback) { if (typeof digest === 'function') { callback = digest; digest = undefined; @@ -551,16 +143,14 @@ exports.pbkdf2 = function(password, 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); -}; + 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) { +function _pbkdf2(password, salt, iterations, keylen, digest, callback) { if (digest === undefined) { throw new TypeError( @@ -570,11 +160,11 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) { password = toBuf(password); salt = toBuf(salt); - if (exports.DEFAULT_ENCODING === 'buffer') + if (cryptoUtil.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; + var encoding = cryptoUtil.DEFAULT_ENCODING; if (callback) { var next = function(er, ret) { if (ret) @@ -587,86 +177,3 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) { 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); -}; - -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 000000000000..037d4ab80812 --- /dev/null +++ b/lib/internal/crypto/certificate.js @@ -0,0 +1,25 @@ +'use strict'; + +const binding = process.binding('crypto'); +const cryptoUtil = require('internal/crypto/util'); +const internalUtil = require('internal/util'); +const toBuf = cryptoUtil.toBuf; + +class Certificate { + verifySpkac(object) { + return binding.certVerifySpkac(object); + } + + exportPublicKey(object, encoding) { + return binding.certExportPublicKey(toBuf(object, encoding)); + } + + exportChallenge(object, encoding) { + return binding.certExportChallenge(toBuf(object, encoding)); + } +} + +module.exports = { + Certificate, + createCertificate: internalUtil.createClassWrapper(Certificate) +}; diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js new file mode 100644 index 000000000000..cad2c94f76dc --- /dev/null +++ b/lib/internal/crypto/cipher.js @@ -0,0 +1,170 @@ +'use strict'; + +const assert = require('assert'); +const binding = process.binding('crypto'); +const cryptoUtil = require('internal/crypto/util'); +const internalUtil = require('internal/util'); +const LazyTransform = require('internal/streams/lazy_transform'); +const StringDecoder = require('string_decoder').StringDecoder; +const util = require('util'); + +const inspect = internalUtil.customInspectSymbol; +const toBuf = cryptoUtil.toBuf; +const kAlgorithm = cryptoUtil.kAlgorithm; + +function getDecoder(decoder, encoding) { + encoding = internalUtil.normalizeEncoding(encoding); + decoder = decoder || new StringDecoder(encoding); + assert(decoder.encoding === encoding, 'Cannot change encoding'); + return decoder; +} + +class CipherBase extends LazyTransform { + constructor(handle, algorithm, decoder, options) { + super(options); + this._handle = handle; + this._decoder = decoder; + this[kAlgorithm] = algorithm; + } + + _transform(chunk, encoding, callback) { + this.push(this._handle.update(chunk, encoding)); + callback(); + } + + _flush(callback) { + try { + this.push(this._handle.final()); + } catch (e) { + callback(e); + return; + } + callback(); + } + + update(data, inputEncoding, outputEncoding) { + inputEncoding = inputEncoding || cryptoUtil.DEFAULT_ENCODING; + outputEncoding = outputEncoding || cryptoUtil.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; + } + + final(outputEncoding) { + outputEncoding = outputEncoding || cryptoUtil.DEFAULT_ENCODING; + var ret = this._handle.final(); + + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding); + ret = this._decoder.end(ret); + } + + return ret; + } + + setAutoPadding(ap) { + this._handle.setAutoPadding(ap); + return this; + } + + getAuthTag() { + return this._handle.getAuthTag(); + } + + setAuthTag(tagbuf) { + this._handle.setAuthTag(tagbuf); + return this; + } + + setAAD(aadbuf) { + this._handle.setAAD(aadbuf); + return this; + } +} + +class Cipher extends CipherBase { + constructor(cipher, password, options) { + const handle = new binding.CipherBase(true); + handle.init(cipher, toBuf(password)); + super(handle, cipher, null, options); + } + + [inspect](depth, options) { + var obj = { + cipher: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `Cipher ${util.inspect(obj, options)}`; + } +} + +class Cipheriv extends CipherBase { + constructor(cipher, key, iv, options) { + const handle = new binding.CipherBase(true); + handle.initiv(cipher, toBuf(key), toBuf(iv)); + super(handle, cipher, null, options); + } + + [inspect](depth, options) { + var obj = { + cipher: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `Cipheriv ${util.inspect(obj, options)}`; + } +} + +class Decipher extends CipherBase { + constructor(cipher, password, options) { + const handle = new binding.CipherBase(false); + handle.init(cipher, toBuf(password)); + super(handle, cipher, null, options); + } + + [inspect](depth, options) { + var obj = { + cipher: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `Decipher ${util.inspect(obj, options)}`; + } +} +Decipher.prototype.finaltol = Decipher.prototype.final; + +class Decipheriv extends CipherBase { + constructor(cipher, key, iv, options) { + const handle = new binding.CipherBase(false); + handle.initiv(cipher, toBuf(key), toBuf(iv)); + super(handle, cipher, null, options); + } + + [inspect](depth, options) { + var obj = { + cipher: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `Decipheriv ${util.inspect(obj, options)}`; + } +} +Decipheriv.prototype.finaltol = Decipheriv.prototype.final; + +module.exports = { + Cipher, + Decipher, + Cipheriv, + Decipheriv, + createCipher: internalUtil.createClassWrapper(Cipher), + createCipheriv: internalUtil.createClassWrapper(Cipheriv), + createDecipher: internalUtil.createClassWrapper(Decipher), + createDecipheriv: internalUtil.createClassWrapper(Decipheriv) +}; diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js new file mode 100644 index 000000000000..78af967e0032 --- /dev/null +++ b/lib/internal/crypto/diffiehellman.js @@ -0,0 +1,207 @@ +'use strict'; + +const binding = process.binding('crypto'); +const Buffer = require('buffer').Buffer; +const constants = process.binding('constants').crypto; +const cryptoUtil = require('internal/crypto/util'); +const internalUtil = require('internal/util'); +const util = require('util'); + +const inspect = internalUtil.customInspectSymbol; +const toBuf = cryptoUtil.toBuf; +const kAlgorithm = cryptoUtil.kAlgorithm; + +const kGenerator = 2; + +class DiffieHellmanBase { + constructor(handle) { + this._handle = handle; + Object.defineProperty(this, 'verifyError', { + enumerable: true, + value: this._handle.verifyError, + writable: false + }); + } + + generateKeys(encoding) { + var keys = this._handle.generateKeys(); + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + keys = keys.toString(encoding); + return keys; + } + + computeSecret(key, inEnc, outEnc) { + inEnc = inEnc || cryptoUtil.DEFAULT_ENCODING; + outEnc = outEnc || cryptoUtil.DEFAULT_ENCODING; + var ret = this._handle.computeSecret(toBuf(key, inEnc)); + if (outEnc && outEnc !== 'buffer') + ret = ret.toString(outEnc); + return ret; + } + + getPrime(encoding) { + var prime = this._handle.getPrime(); + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + prime = prime.toString(encoding); + return prime; + } + + getGenerator(encoding) { + var generator = this._handle.getGenerator(); + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + generator = generator.toString(encoding); + return generator; + } + + getPublicKey(encoding) { + var key = this._handle.getPublicKey(); + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; + } + + getPrivateKey(encoding) { + var key = this._handle.getPrivateKey(); + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; + } +} + +class DiffieHellman extends DiffieHellmanBase { + constructor(sizeOrKey, keyEncoding, generator, genEncoding) { + if (!(sizeOrKey instanceof Buffer) && + typeof sizeOrKey !== 'number' && + typeof sizeOrKey !== 'string') + throw new TypeError('First argument should be number, string or Buffer'); + + if (keyEncoding) { + if (typeof keyEncoding !== 'string' || + (!Buffer.isEncoding(keyEncoding) && keyEncoding !== 'buffer')) { + genEncoding = generator; + generator = keyEncoding; + keyEncoding = false; + } + } + + keyEncoding = keyEncoding || cryptoUtil.DEFAULT_ENCODING; + genEncoding = genEncoding || cryptoUtil.DEFAULT_ENCODING; + + if (typeof sizeOrKey !== 'number') + sizeOrKey = toBuf(sizeOrKey, keyEncoding); + + if (!generator) + generator = kGenerator; + else if (typeof generator !== 'number') + generator = toBuf(generator, genEncoding); + + super(new binding.DiffieHellman(sizeOrKey, generator)); + } + + setPublicKey(key, encoding) { + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + this._handle.setPublicKey(toBuf(key, encoding)); + return this; + } + + setPrivateKey(key, encoding) { + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + this._handle.setPrivateKey(toBuf(key, encoding)); + return this; + } + + [inspect](depth, options) { + var obj = { + generator: this.getGenerator('hex'), + prime: this.getPrime('hex') + }; + if (this._options) + obj.options = this._options; + return `DiffieHellman ${util.inspect(obj, options)}`; + } +} + +class DiffieHellmanGroup extends DiffieHellmanBase { + constructor(name) { + super(new binding.DiffieHellmanGroup(name)); + this[kAlgorithm] = name; + } + + [inspect](depth, options) { + var obj = { + name: this[kAlgorithm], + generator: this.getGenerator('hex'), + prime: this.getPrime('hex') + }; + if (this._options) + obj.options = this._options; + return `DiffieHellmanGroup ${util.inspect(obj, options)}`; + } +} + +class ECDH { + constructor(curve) { + this._handle = new binding.ECDH(curve); + this[kAlgorithm] = curve; + } + + generateKeys(encoding, format) { + this._handle.generateKeys(); + return this.getPublicKey(encoding, format); + } + + getPublicKey(encoding, format) { + var f = constants.POINT_CONVERSION_UNCOMPRESSED; + if (format !== undefined) { + switch (format) { + case 'compressed': + f = constants.POINT_CONVERSION_COMPRESSED; + break; + case 'hybrid': + f = constants.POINT_CONVERSION_HYBRID; + break; + case 'uncompressed': + f = constants.POINT_CONVERSION_UNCOMPRESSED; + break; + default: + throw new TypeError(`Bad format: ${format}`); + } + } + var key = this._handle.getPublicKey(f); + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; + } + + [inspect](depth, options) { + var obj = { + curve: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `ECDH ${util.inspect(obj, options)}`; + } +} +ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret; +ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey; +ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey; +ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey; + +function createECDH(curve) { + return new ECDH(curve); +} + +module.exports = { + DiffieHellman, + DiffieHellmanGroup, + ECDH, + createDiffieHellman: internalUtil.createClassWrapper(DiffieHellman), + createDiffieHellmanGroup: internalUtil.createClassWrapper(DiffieHellmanGroup), + createECDH +}; diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js new file mode 100644 index 000000000000..432adec28c85 --- /dev/null +++ b/lib/internal/crypto/hash.js @@ -0,0 +1,79 @@ +'use strict'; + +const binding = process.binding('crypto'); +const cryptoUtil = require('internal/crypto/util'); +const internalUtil = require('internal/util'); +const LazyTransform = require('internal/streams/lazy_transform'); +const util = require('util'); + +const inspect = internalUtil.customInspectSymbol; +const toBuf = cryptoUtil.toBuf; +const kAlgorithm = cryptoUtil.kAlgorithm; + +class HashBase extends LazyTransform { + constructor(handle, algorithm, options) { + super(options); + this._handle = handle; + this[kAlgorithm] = algorithm; + } + + _transform(chunk, encoding, callback) { + this._handle.update(chunk, encoding); + callback(); + } + + _flush(callback) { + this.push(this._handle.digest()); + callback(); + } + + update(data, encoding) { + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + this._handle.update(data, encoding); + return this; + } + + digest(outputEncoding) { + outputEncoding = outputEncoding || cryptoUtil.DEFAULT_ENCODING; + return this._handle.digest(outputEncoding); + } +} + +class Hash extends HashBase { + constructor(algorithm, options) { + super(new binding.Hash(algorithm), algorithm, options); + } + + [inspect](depth, options) { + var obj = { + algorithm: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `Hash ${util.inspect(obj, options)}`; + } +} + +class Hmac extends HashBase { + constructor(hmac, key, options) { + const handle = new binding.Hmac(); + handle.init(hmac, toBuf(key)); + super(handle, hmac, options); + } + + [inspect](depth, options) { + var obj = { + hmac: this[kAlgorithm] + }; + if (this._options) + obj.options = this._options; + return `Hmac ${util.inspect(obj, options)}`; + } +} + +module.exports = { + Hash, + Hmac, + createHash: internalUtil.createClassWrapper(Hash), + createHmac: internalUtil.createClassWrapper(Hmac) +}; diff --git a/lib/internal/crypto/sign.js b/lib/internal/crypto/sign.js new file mode 100644 index 000000000000..aa35507b7b71 --- /dev/null +++ b/lib/internal/crypto/sign.js @@ -0,0 +1,104 @@ +'use strict'; + +const binding = process.binding('crypto'); +const cryptoUtil = require('internal/crypto/util'); +const internalUtil = require('internal/util'); +const Writable = require('stream').Writable; +const util = require('util'); + +const inspect = internalUtil.customInspectSymbol; +const toBuf = cryptoUtil.toBuf; +const kAlgorithm = cryptoUtil.kAlgorithm; + +class SignBase extends Writable { + constructor(handle, options) { + super(options); + this._handle = handle; + } + + _write(chunk, encoding, callback) { + this._handle.update(chunk, encoding); + callback(); + } + + update(data, encoding) { + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + this._handle.update(data, encoding); + return this; + } +} + +class Sign extends SignBase { + constructor(algorithm, options) { + const handle = new binding.Sign(); + handle.init(algorithm); + super(handle, options); + this[kAlgorithm] = algorithm; + } + + sign(options, encoding) { + if (!options) + throw new Error('No key provided to sign'); + + var key = options.key || options; + var passphrase = options.passphrase || null; + var ret = this._handle.sign(toBuf(key), null, passphrase); + + encoding = encoding || cryptoUtil.DEFAULT_ENCODING; + if (encoding && encoding !== 'buffer') + ret = ret.toString(encoding); + + return ret; + } + + [inspect](depth, options) { + var obj = { + algorithm: this[kAlgorithm], + _writableState: this._writableState, + writable: this.writable, + domain: this.domain, + _events: this._events, + _eventsCount: this._eventsCount, + _maxListeners: this._maxListeners + }; + if (this._options) + obj.options = this._options; + return `Sign ${util.inspect(obj, options)}`; + } +} + +class Verify extends SignBase { + constructor(algorithm, options) { + const handle = new binding.Verify(); + handle.init(algorithm); + super(handle, options); + this[kAlgorithm] = algorithm; + } + + verify(object, signature, sigEncoding) { + sigEncoding = sigEncoding || cryptoUtil.DEFAULT_ENCODING; + return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding)); + } + + [inspect](depth, options) { + var obj = { + algorithm: this[kAlgorithm], + _writableState: this._writableState, + writable: this.writable, + domain: this.domain, + _events: this._events, + _eventsCount: this._eventsCount, + _maxListeners: this._maxListeners + }; + if (this._options) + obj.options = this._options; + return `Verify ${util.inspect(obj, options)}`; + } +} + +module.exports = { + Sign, + Verify, + createSign: internalUtil.createClassWrapper(Sign), + createVerify: internalUtil.createClassWrapper(Verify) +}; diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js new file mode 100644 index 000000000000..0cab62965760 --- /dev/null +++ b/lib/internal/crypto/util.js @@ -0,0 +1,18 @@ +'use strict'; + +const Buffer = require('buffer').Buffer; + +function toBuf(str, encoding) { + if (typeof str === 'string') { + if (encoding === 'buffer' || !encoding) + encoding = 'utf8'; + return Buffer.from(str, encoding); + } + return str; +} + +module.exports = { + toBuf, + DEFAULT_ENCODING: 'buffer', + kAlgorithm: Symbol('algorithm') +}; diff --git a/node.gyp b/node.gyp index 0d5b6c27ef60..cb51b6476041 100644 --- a/node.gyp +++ b/node.gyp @@ -83,6 +83,12 @@ 'lib/internal/cluster/utils.js', 'lib/internal/cluster/worker.js', 'lib/internal/errors.js', + 'lib/internal/crypto/certificate.js', + 'lib/internal/crypto/cipher.js', + 'lib/internal/crypto/diffiehellman.js', + 'lib/internal/crypto/hash.js', + 'lib/internal/crypto/sign.js', + 'lib/internal/crypto/util.js', 'lib/internal/freelist.js', 'lib/internal/fs.js', 'lib/internal/linkedlist.js', diff --git a/test/parallel/test-crypto-dh.js b/test/parallel/test-crypto-dh.js index b95863a13ff9..1c0d546249a9 100644 --- a/test/parallel/test-crypto-dh.js +++ b/test/parallel/test-crypto-dh.js @@ -285,4 +285,4 @@ ecdh5.setPrivateKey(cafebabeKey, 'hex'); // invalid test: curve argument is undefined assert.throws(() => { crypto.createECDH(); -}, /^TypeError: "curve" argument should be a string$/); +}, /^TypeError: ECDH curve name must be a string$/);