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/internal/crypto | |
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/internal/crypto')
-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 |
8 files changed, 953 insertions, 0 deletions
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 +}; |