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>
This commit is contained in:
James M Snell 2017-09-06 08:10:34 -07:00
parent 8fa5fcc0ba
commit c75f87cc4c
25 changed files with 1690 additions and 1008 deletions

View File

@ -48,7 +48,60 @@ The `crypto` module provides the `Certificate` class for working with SPKAC
data. The most common usage is handling output generated by the HTML5
`<keygen>` element. Node.js uses [OpenSSL's SPKAC implementation][] internally.
### new crypto.Certificate()
### Certificate.exportChallenge(spkac)
<!-- YAML
added: REPLACEME
-->
- `spkac` {string | Buffer | TypedArray | DataView}
- Returns {Buffer} The challenge component of the `spkac` data structure, which
includes a public key and a challenge.
```js
const { Certificate } = require('crypto');
const spkac = getSpkacSomehow();
const challenge = Certificate.exportChallenge(spkac);
console.log(challenge.toString('utf8'));
// Prints: the challenge as a UTF8 string
```
### Certificate.exportPublicKey(spkac)
<!-- YAML
added: REPLACEME
-->
- `spkac` {string | Buffer | TypedArray | DataView}
- Returns {Buffer} The public key component of the `spkac` data structure,
which includes a public key and a challenge.
```js
const { Certificate } = require('crypto');
const spkac = getSpkacSomehow();
const publicKey = Certificate.exportPublicKey(spkac);
console.log(publicKey);
// Prints: the public key as <Buffer ...>
```
### Certificate.verifySpkac(spkac)
<!-- YAML
added: REPLACEME
-->
- `spkac` {Buffer | TypedArray | DataView}
- Returns {boolean} `true` if the given `spkac` data structure is valid, `false`
otherwise.
```js
const { Certificate } = require('crypto');
const spkac = getSpkacSomehow();
console.log(Certificate.verifySpkac(Buffer.from(spkac)));
// Prints: true or false
```
### Legacy API
As a still supported legacy interface, it is possible (but not recommended) to
create new instances of the `crypto.Certificate` class as illustrated in the
examples below.
#### new crypto.Certificate()
Instances of the `Certificate` class can be created using the `new` keyword
or by calling `crypto.Certificate()` as a function:
@ -60,7 +113,7 @@ const cert1 = new crypto.Certificate();
const cert2 = crypto.Certificate();
```
### certificate.exportChallenge(spkac)
#### certificate.exportChallenge(spkac)
<!-- YAML
added: v0.11.8
-->
@ -76,7 +129,7 @@ console.log(challenge.toString('utf8'));
// Prints: the challenge as a UTF8 string
```
### certificate.exportPublicKey(spkac)
#### certificate.exportPublicKey(spkac)
<!-- YAML
added: v0.11.8
-->
@ -92,7 +145,7 @@ console.log(publicKey);
// Prints: the public key as <Buffer ...>
```
### certificate.verifySpkac(spkac)
#### certificate.verifySpkac(spkac)
<!-- YAML
added: v0.11.8
-->
@ -1747,9 +1800,13 @@ negative performance implications for some applications, see the
### crypto.randomFillSync(buffer[, offset][, size])
<!-- YAML
added: v7.10.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/15231
description: The `buffer` argument may be any ArrayBufferView
-->
* `buffer` {Buffer|Uint8Array} Must be supplied.
* `buffer` {Buffer|Uint8Array|ArrayBufferView} Must be supplied.
* `offset` {number} Defaults to `0`.
* `size` {number} Defaults to `buffer.length - offset`.
@ -1769,12 +1826,29 @@ crypto.randomFillSync(buf, 5, 5);
console.log(buf.toString('hex'));
```
Any `TypedArray` or `DataView` instance may be passed as `buffer`.
```js
const a = new Uint32Array(10);
console.log(crypto.randomFillSync(a).toString('hex'));
const b = new Float64Array(10);
console.log(crypto.randomFillSync(a).toString('hex'));
const c = new DataView(new ArrayBuffer(10));
console.log(crypto.randomFillSync(a).toString('hex'));
```
### crypto.randomFill(buffer[, offset][, size], callback)
<!-- YAML
added: v7.10.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/15231
description: The `buffer` argument may be any ArrayBufferView
-->
* `buffer` {Buffer|Uint8Array} Must be supplied.
* `buffer` {Buffer|Uint8Array|ArrayBufferView} Must be supplied.
* `offset` {number} Defaults to `0`.
* `size` {number} Defaults to `buffer.length - offset`.
* `callback` {Function} `function(err, buf) {}`.
@ -1804,6 +1878,28 @@ crypto.randomFill(buf, 5, 5, (err, buf) => {
});
```
Any `TypedArray` or `DataView` instance may be passed as `buffer`.
```js
const a = new Uint32Array(10);
crypto.randomFill(a, (err, buf) => {
if (err) throw err;
console.log(buf.toString('hex'));
});
const b = new Float64Array(10);
crypto.randomFill(b, (err, buf) => {
if (err) throw err;
console.log(buf.toString('hex'));
});
const c = new DataView(new ArrayBuffer(10));
crypto.randomFill(c, (err, buf) => {
if (err) throw err;
console.log(buf.toString('hex'));
});
```
Note that this API uses libuv's threadpool, which can have surprising and
negative performance implications for some applications, see the
[`UV_THREADPOOL_SIZE`][] documentation for more information.

View File

@ -621,6 +621,12 @@ Used when `Console` is instantiated without `stdout` stream or when `stdout` or
Used when the native call from `process.cpuUsage` cannot be processed properly.
<a id="ERR_CRYPTO_ECDH_INVALID_FORMAT"></a>
### ERR_CRYPTO_ECDH_INVALID_FORMAT
Used when an invalid value for the `format` argument has been passed to the
`crypto.ECDH()` class `getPublicKey()` method.
<a id="ERR_DNS_SET_SERVERS_FAILED"></a>
### ERR_DNS_SET_SERVERS_FAILED

View File

@ -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);
};
}
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', {
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', {
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;
};
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) {
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);
};
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);
}
}
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,
exports.Certificate = Certificate;
function Certificate() {
if (!(this instanceof Certificate))
return new Certificate();
}
Certificate.prototype.verifySpkac = function verifySpkac(object) {
return binding.certVerifySpkac(object);
// Classes
Certificate,
Cipher,
Cipheriv,
Decipher,
Decipheriv,
DiffieHellman,
DiffieHellmanGroup,
Hash,
Hmac,
Sign,
Verify
};
Object.defineProperties(exports, {
fips: {
get: getFipsCrypto,
set: setFipsCrypto
},
DEFAULT_ENCODING: {
enumerable: true,
configurable: true,
get: getDefaultEncoding,
set: setDefaultEncoding
},
constants: {
configurable: false,
enumerable: true,
value: constants
},
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');
// 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')
}
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');
}
}
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')
});

View File

@ -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;

View File

@ -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,
};

View File

@ -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
};

125
lib/internal/crypto/hash.js Normal file
View File

@ -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
};

View File

@ -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
};

View File

@ -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
};

131
lib/internal/crypto/sig.js Normal file
View File

@ -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
};

View File

@ -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
};

View File

@ -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');

View File

@ -84,6 +84,14 @@
'lib/internal/cluster/shared_handle.js',
'lib/internal/cluster/utils.js',
'lib/internal/cluster/worker.js',
'lib/internal/crypto/certificate.js',
'lib/internal/crypto/cipher.js',
'lib/internal/crypto/diffiehellman.js',
'lib/internal/crypto/hash.js',
'lib/internal/crypto/pbkdf2.js',
'lib/internal/crypto/random.js',
'lib/internal/crypto/sig.js',
'lib/internal/crypto/util.js',
'lib/internal/encoding.js',
'lib/internal/errors.js',
'lib/internal/freelist.js',

View File

@ -3699,7 +3699,6 @@ void Hmac::New(const FunctionCallbackInfo<Value>& args) {
void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
HandleScope scope(env()->isolate());
CHECK_EQ(initialised_, false);
const EVP_MD* md = EVP_get_digestbyname(hash_type);
if (md == nullptr) {
return env()->ThrowError("Unknown message digest");
@ -3720,13 +3719,6 @@ void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder());
Environment* env = hmac->env();
if (args.Length() < 2) {
return env->ThrowError("Hash type and key arguments are mandatory");
}
THROW_AND_RETURN_IF_NOT_STRING(args[0], "Hash type");
THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key");
const node::Utf8Value hash_type(env->isolate(), args[0]);
const char* buffer_data = Buffer::Data(args[1]);
size_t buffer_length = Buffer::Length(args[1]);
@ -3748,24 +3740,22 @@ void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
Hmac* hmac;
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder());
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data");
// Only copy the data if we have to, because it's a string
bool r;
bool r = true;
if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder;
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8))
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8)) {
args.GetReturnValue().Set(false);
return;
}
r = hmac->HmacUpdate(decoder.out(), decoder.size());
} else {
} else if (args[0]->IsArrayBufferView()) {
char* buf = Buffer::Data(args[0]);
size_t buflen = Buffer::Length(args[0]);
r = hmac->HmacUpdate(buf, buflen);
}
if (!r) {
return env->ThrowTypeError("HmacUpdate fail");
}
args.GetReturnValue().Set(r);
}
@ -3777,13 +3767,9 @@ void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
enum encoding encoding = BUFFER;
if (args.Length() >= 1) {
CHECK(args[0]->IsString());
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}
if (encoding == UCS2) {
return env->ThrowError("hmac.digest() does not support UTF-16");
}
CHECK_NE(encoding, UCS2); // Digest does not support UTF-16
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len = 0;
@ -3825,10 +3811,6 @@ void Hash::Initialize(Environment* env, v8::Local<v8::Object> target) {
void Hash::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (args.Length() == 0 || !args[0]->IsString()) {
return env->ThrowError("Must give hashtype string as argument");
}
const node::Utf8Value hash_type(env->isolate(), args[0]);
Hash* hash = new Hash(env, args.This());
@ -3840,7 +3822,6 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {
bool Hash::HashInit(const char* hash_type) {
CHECK_EQ(initialised_, false);
const EVP_MD* md = EVP_get_digestbyname(hash_type);
if (md == nullptr)
return false;
@ -3868,31 +3849,22 @@ void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
Hash* hash;
ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data");
if (!hash->initialised_) {
return env->ThrowError("Not initialized");
}
if (hash->finalized_) {
return env->ThrowError("Digest already called");
}
// Only copy the data if we have to, because it's a string
bool r;
bool r = true;
if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder;
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8))
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8)) {
args.GetReturnValue().Set(false);
return;
}
r = hash->HashUpdate(decoder.out(), decoder.size());
} else {
} else if (args[0]->IsArrayBufferView()) {
char* buf = Buffer::Data(args[0]);
size_t buflen = Buffer::Length(args[0]);
r = hash->HashUpdate(buf, buflen);
}
if (!r) {
return env->ThrowTypeError("HashUpdate fail");
}
args.GetReturnValue().Set(r);
}
@ -3902,23 +3874,11 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
Hash* hash;
ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
if (!hash->initialised_) {
return env->ThrowError("Not initialized");
}
if (hash->finalized_) {
return env->ThrowError("Digest already called");
}
enum encoding encoding = BUFFER;
if (args.Length() >= 1) {
CHECK(args[0]->IsString());
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}
if (encoding == UCS2) {
return env->ThrowError("hash.digest() does not support UTF-16");
}
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len;
@ -5562,13 +5522,14 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> (*argv)[2]) {
req->object()->Get(req->env()->context(),
req->env()->buffer_string()).ToLocalChecked();
if (buffer->IsUint8Array()) {
if (buffer->IsArrayBufferView()) {
CHECK_LE(req->size(), Buffer::Length(buffer));
char* buf = Buffer::Data(buffer);
memcpy(buf, data, req->size());
(*argv)[1] = buffer;
} else {
(*argv)[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
(*argv)[1] = Buffer::New(req->env(), data, size)
.ToLocalChecked();
}
}
}
@ -5649,7 +5610,7 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsUint8Array());
CHECK(args[0]->IsArrayBufferView());
CHECK(args[1]->IsUint32());
CHECK(args[2]->IsUint32());

View File

@ -19,6 +19,7 @@ using v8::Value;
#define VALUE_METHOD_MAP(V) \
V(isArrayBuffer, IsArrayBuffer) \
V(isArrayBufferView, IsArrayBufferView) \
V(isAsyncFunction, IsAsyncFunction) \
V(isDataView, IsDataView) \
V(isDate, IsDate) \

View File

@ -568,9 +568,12 @@ testCipher4(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678'));
// update() should only take buffers / strings
assert.throws(function() {
crypto.createHash('sha1').update({ foo: 'bar' });
}, /^TypeError: Data must be a string or a buffer$/);
common.expectsError(
() => crypto.createHash('sha1').update({ foo: 'bar' }),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError
});
// Test Diffie-Hellman with two parties sharing a secret,

View File

@ -26,6 +26,7 @@ if (!common.hasCrypto)
const assert = require('assert');
const crypto = require('crypto');
const { Certificate } = crypto;
const fixtures = require('../common/fixtures');
crypto.DEFAULT_ENCODING = 'buffer';
@ -35,26 +36,47 @@ const spkacValid = fixtures.readSync('spkac.valid');
const spkacFail = fixtures.readSync('spkac.fail');
const spkacPem = fixtures.readSync('spkac.pem');
const certificate = new crypto.Certificate();
{
// Test instance methods
const certificate = new Certificate();
assert.strictEqual(certificate.verifySpkac(spkacValid), true);
assert.strictEqual(certificate.verifySpkac(spkacFail), false);
assert.strictEqual(certificate.verifySpkac(spkacValid), true);
assert.strictEqual(certificate.verifySpkac(spkacFail), false);
assert.strictEqual(
stripLineEndings(certificate.exportPublicKey(spkacValid).toString('utf8')),
stripLineEndings(spkacPem.toString('utf8'))
);
assert.strictEqual(certificate.exportPublicKey(spkacFail), '');
assert.strictEqual(
stripLineEndings(certificate.exportPublicKey(spkacValid).toString('utf8')),
stripLineEndings(spkacPem.toString('utf8'))
);
assert.strictEqual(certificate.exportPublicKey(spkacFail), '');
assert.strictEqual(
certificate.exportChallenge(spkacValid).toString('utf8'),
'fb9ab814-6677-42a4-a60c-f905d1a6924d'
);
assert.strictEqual(certificate.exportChallenge(spkacFail), '');
assert.strictEqual(
certificate.exportChallenge(spkacValid).toString('utf8'),
'fb9ab814-6677-42a4-a60c-f905d1a6924d'
);
assert.strictEqual(certificate.exportChallenge(spkacFail), '');
}
{
// Test static methods
assert.strictEqual(Certificate.verifySpkac(spkacValid), true);
assert.strictEqual(Certificate.verifySpkac(spkacFail), false);
assert.strictEqual(
stripLineEndings(Certificate.exportPublicKey(spkacValid).toString('utf8')),
stripLineEndings(spkacPem.toString('utf8'))
);
assert.strictEqual(Certificate.exportPublicKey(spkacFail), '');
assert.strictEqual(
Certificate.exportChallenge(spkacValid).toString('utf8'),
'fb9ab814-6677-42a4-a60c-f905d1a6924d'
);
assert.strictEqual(Certificate.exportChallenge(spkacFail), '');
}
function stripLineEndings(obj) {
return obj.replace(/\n/g, '');
}
// direct call Certificate() should return instance
assert(crypto.Certificate() instanceof crypto.Certificate);
assert(Certificate() instanceof Certificate);

View File

@ -22,24 +22,22 @@ assert.strictEqual(secret2.toString('base64'), secret1);
assert.strictEqual(dh1.verifyError, 0);
assert.strictEqual(dh2.verifyError, 0);
const argumentsError =
/^TypeError: First argument should be number, string, Buffer, TypedArray, or DataView$/;
assert.throws(() => {
crypto.createDiffieHellman([0x1, 0x2]);
}, argumentsError);
assert.throws(() => {
crypto.createDiffieHellman(() => { });
}, argumentsError);
assert.throws(() => {
crypto.createDiffieHellman(/abc/);
}, argumentsError);
assert.throws(() => {
crypto.createDiffieHellman({});
}, argumentsError);
[
[0x1, 0x2],
() => { },
/abc/,
{}
].forEach((i) => {
common.expectsError(
() => crypto.createDiffieHellman(i),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "sizeOrKey" argument must be one of type number, string, ' +
'Buffer, TypedArray, or DataView'
}
);
});
// Create "another dh1" using generated keys from dh1,
// and compute secret again
@ -198,9 +196,14 @@ if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) {
firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0];
assert(firstByte === 6 || firstByte === 7);
// format value should be string
assert.throws(() => {
ecdh1.getPublicKey('buffer', 10);
}, /^TypeError: Bad format: 10$/);
common.expectsError(
() => ecdh1.getPublicKey('buffer', 10),
{
code: 'ERR_CRYPTO_ECDH_INVALID_FORMAT',
type: TypeError,
message: 'Invalid ECDH format: 10'
});
// ECDH should check that point is on curve
const ecdh3 = crypto.createECDH('secp256k1');
@ -331,6 +334,10 @@ if (availableCurves.has('prime256v1') && availableHashes.has('sha256')) {
}
// invalid test: curve argument is undefined
assert.throws(() => {
crypto.createECDH();
}, /^TypeError: "curve" argument should be a string$/);
common.expectsError(
() => crypto.createECDH(),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "curve" argument must be of type string'
});

View File

@ -4,13 +4,20 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const crypto = require('crypto');
assert.throws(function() {
crypto.setEngine(true);
}, /^TypeError: "id" argument should be a string$/);
common.expectsError(
() => crypto.setEngine(true),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "id" argument must be of type string'
});
assert.throws(function() {
crypto.setEngine('/path/to/engine', 'notANumber');
}, /^TypeError: "flags" argument should be a number, if present$/);
common.expectsError(
() => crypto.setEngine('/path/to/engine', 'notANumber'),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "flags" argument must be of type number'
});

View File

@ -105,14 +105,34 @@ assert.notStrictEqual(
const h3 = crypto.createHash('sha256');
h3.digest();
assert.throws(function() {
h3.digest();
}, /Digest already called/);
assert.throws(function() {
h3.update('foo');
}, /Digest already called/);
common.expectsError(
() => h3.digest(),
{
code: 'ERR_CRYPTO_HASH_FINALIZED',
type: Error
});
assert.throws(function() {
crypto.createHash('sha256').digest('ucs2');
}, /^Error: hash\.digest\(\) does not support UTF-16$/);
common.expectsError(
() => h3.update('foo'),
{
code: 'ERR_CRYPTO_HASH_FINALIZED',
type: Error
});
common.expectsError(
() => crypto.createHash('sha256').digest('ucs2'),
{
code: 'ERR_CRYPTO_HASH_DIGEST_NO_UTF16',
type: Error
}
);
common.expectsError(
() => crypto.createHash(),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "algorithm" argument must be of type string'
}
);

View File

@ -6,19 +6,11 @@ if (!common.hasCrypto)
const assert = require('assert');
const crypto = require('crypto');
// Test for binding layer robustness
{
const binding = process.binding('crypto');
const h = new binding.Hmac();
// Fail to init the Hmac with an algorithm.
assert.throws(() => h.update('hello'), /^TypeError: HmacUpdate fail$/);
}
// Test HMAC
const h1 = crypto.createHmac('sha1', 'Node')
.update('some data')
.update('to hmac')
.digest('hex');
.update('some data')
.update('to hmac')
.digest('hex');
assert.strictEqual(h1, '19fd6e1ba73d9ed2224dd5094a71babe85d9a892', 'test HMAC');
// Test HMAC (Wikipedia Test Cases)
@ -376,9 +368,12 @@ for (let i = 0, l = rfc2202_sha1.length; i < l; i++) {
);
}
assert.throws(function() {
crypto.createHmac('sha256', 'w00t').digest('ucs2');
}, /^Error: hmac\.digest\(\) does not support UTF-16$/);
common.expectsError(
() => crypto.createHmac('sha256', 'w00t').digest('ucs2'),
{
code: 'ERR_CRYPTO_HASH_DIGEST_NO_UTF16',
type: Error
});
// Check initialized -> uninitialized state transition after calling digest().
{

View File

@ -55,9 +55,13 @@ function ondone(err, key) {
}
// Error path should not leak memory (check with valgrind).
assert.throws(function() {
crypto.pbkdf2('password', 'salt', 1, 20, null);
}, /^Error: No callback provided to pbkdf2$/);
common.expectsError(
() => crypto.pbkdf2('password', 'salt', 1, 20, null),
{
code: 'ERR_INVALID_CALLBACK',
type: TypeError
}
);
// Should not work with Infinity key length
assert.throws(function() {
@ -95,10 +99,18 @@ assert.doesNotThrow(() => {
}));
});
assert.throws(() => {
crypto.pbkdf2('password', 'salt', 8, 8, common.mustNotCall());
}, /^TypeError: The "digest" argument is required and must not be undefined$/);
common.expectsError(
() => crypto.pbkdf2('password', 'salt', 8, 8, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "digest" argument must be one of type string or null'
});
assert.throws(() => {
crypto.pbkdf2Sync('password', 'salt', 8, 8);
}, /^TypeError: The "digest" argument is required and must not be undefined$/);
common.expectsError(
() => crypto.pbkdf2Sync('password', 'salt', 8, 8),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "digest" argument must be one of type string or null'
});

View File

@ -33,15 +33,6 @@ crypto.DEFAULT_ENCODING = 'buffer';
// bump, we register a lot of exit listeners
process.setMaxListeners(256);
const errMessages = {
offsetNotNumber: /^TypeError: offset must be a number$/,
offsetOutOfRange: /^RangeError: offset out of range$/,
offsetNotUInt32: /^TypeError: offset must be a uint32$/,
sizeNotNumber: /^TypeError: size must be a number$/,
sizeNotUInt32: /^TypeError: size must be a uint32$/,
bufferTooSmall: /^RangeError: buffer too small$/,
};
const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/;
[crypto.randomBytes, crypto.pseudoRandomBytes].forEach(function(f) {
[-1, undefined, null, false, true, {}, []].forEach(function(value) {
@ -74,6 +65,46 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/;
assert.notStrictEqual(before, after);
}
{
const buf = new Uint16Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFillSync(buf);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}
{
const buf = new Uint32Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFillSync(buf);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}
{
const buf = new Float32Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFillSync(buf);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}
{
const buf = new Float64Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFillSync(buf);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}
{
const buf = new DataView(new ArrayBuffer(10));
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFillSync(buf);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}
{
const buf = Buffer.alloc(10);
const before = buf.toString('hex');
@ -94,6 +125,56 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/;
}));
}
{
const buf = new Uint16Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFill(buf, common.mustCall((err, buf) => {
assert.ifError(err);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}));
}
{
const buf = new Uint32Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFill(buf, common.mustCall((err, buf) => {
assert.ifError(err);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}));
}
{
const buf = new Float32Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFill(buf, common.mustCall((err, buf) => {
assert.ifError(err);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}));
}
{
const buf = new Float64Array(10);
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFill(buf, common.mustCall((err, buf) => {
assert.ifError(err);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}));
}
{
const buf = new DataView(new ArrayBuffer(10));
const before = Buffer.from(buf.buffer).toString('hex');
crypto.randomFill(buf, common.mustCall((err, buf) => {
assert.ifError(err);
const after = Buffer.from(buf.buffer).toString('hex');
assert.notStrictEqual(before, after);
}));
}
{
const buf = Buffer.alloc(10);
const before = buf.toString('hex');
@ -155,108 +236,228 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/;
const len = Buffer.byteLength(buf);
assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`);
assert.throws(() => {
crypto.randomFillSync(buf, 'test');
}, errMessages.offsetNotNumber);
common.expectsError(
() => crypto.randomFillSync(buf, 'test'),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, NaN);
}, errMessages.offsetNotNumber);
common.expectsError(
() => crypto.randomFillSync(buf, NaN),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFill(buf, 'test', common.mustNotCall());
}, errMessages.offsetNotNumber);
common.expectsError(
() => crypto.randomFill(buf, 'test', common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFill(buf, NaN, common.mustNotCall());
}, errMessages.offsetNotNumber);
common.expectsError(
() => crypto.randomFill(buf, NaN, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, 11);
}, errMessages.offsetOutOfRange);
common.expectsError(
() => crypto.randomFillSync(buf, 11),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "offset" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, max);
}, errMessages.offsetOutOfRange);
common.expectsError(
() => crypto.randomFillSync(buf, max),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "offset" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFill(buf, 11, common.mustNotCall());
}, errMessages.offsetOutOfRange);
common.expectsError(
() => crypto.randomFill(buf, 11, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "offset" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFill(buf, max, common.mustNotCall());
}, errMessages.offsetOutOfRange);
common.expectsError(
() => crypto.randomFill(buf, max, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "offset" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, 0, 'test');
}, errMessages.sizeNotNumber);
common.expectsError(
() => crypto.randomFillSync(buf, 0, 'test'),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, 0, NaN);
}, errMessages.sizeNotNumber);
common.expectsError(
() => crypto.randomFillSync(buf, 0, NaN),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFill(buf, 0, 'test', common.mustNotCall());
}, errMessages.sizeNotNumber);
common.expectsError(
() => crypto.randomFill(buf, 0, 'test', common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
assert.throws(() => {
crypto.randomFill(buf, 0, NaN, common.mustNotCall());
}, errMessages.sizeNotNumber);
common.expectsError(
() => crypto.randomFill(buf, 0, NaN, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
{
const size = (-1 >>> 0) + 1;
assert.throws(() => {
crypto.randomFillSync(buf, 0, -10);
}, errMessages.sizeNotUInt32);
common.expectsError(
() => crypto.randomFillSync(buf, 0, -10),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, 0, size);
}, errMessages.sizeNotUInt32);
common.expectsError(
() => crypto.randomFillSync(buf, 0, size),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
assert.throws(() => {
crypto.randomFill(buf, 0, -10, common.mustNotCall());
}, errMessages.sizeNotUInt32);
common.expectsError(
() => crypto.randomFill(buf, 0, -10, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
assert.throws(() => {
crypto.randomFill(buf, 0, size, common.mustNotCall());
}, errMessages.sizeNotUInt32);
common.expectsError(
() => crypto.randomFill(buf, 0, size, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
}
assert.throws(() => {
crypto.randomFillSync(buf, -10);
}, errMessages.offsetNotUInt32);
common.expectsError(
() => crypto.randomFillSync(buf, -10),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
assert.throws(() => {
crypto.randomFill(buf, -10, common.mustNotCall());
}, errMessages.offsetNotUInt32);
common.expectsError(
() => crypto.randomFill(buf, -10, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, 1, 10);
}, errMessages.bufferTooSmall);
common.expectsError(
() => crypto.randomFillSync(buf, 1, 10),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "size" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFill(buf, 1, 10, common.mustNotCall());
}, errMessages.bufferTooSmall);
common.expectsError(
() => crypto.randomFill(buf, 1, 10, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "size" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFillSync(buf, 0, 12);
}, errMessages.bufferTooSmall);
common.expectsError(
() => crypto.randomFillSync(buf, 0, 12),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "size" argument is out of range'
}
);
assert.throws(() => {
crypto.randomFill(buf, 0, 12, common.mustNotCall());
}, errMessages.bufferTooSmall);
common.expectsError(
() => crypto.randomFill(buf, 0, 12, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The "size" argument is out of range'
}
);
{
// Offset is too big
const offset = (-1 >>> 0) + 1;
assert.throws(() => {
crypto.randomFillSync(buf, offset, 10);
}, errMessages.offsetNotUInt32);
common.expectsError(
() => crypto.randomFillSync(buf, offset, 10),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
assert.throws(() => {
crypto.randomFill(buf, offset, 10, common.mustNotCall());
}, errMessages.offsetNotUInt32);
common.expectsError(
() => crypto.randomFill(buf, offset, 10, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
}
}
}
@ -265,4 +466,21 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/;
// length exceeds max acceptable value"
assert.throws(function() {
crypto.randomBytes((-1 >>> 0) + 1);
}, errMessages.sizeNotUInt32);
}, /^TypeError: size must be a uint32$/);
[1, true, NaN, null, undefined, {}, []].forEach((i) => {
common.expectsError(
() => crypto.randomFillSync(i),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError
}
);
common.expectsError(
() => crypto.randomFill(i, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError
}
);
});

View File

@ -197,21 +197,21 @@ const modSize = 1024;
// Test exceptions for invalid `padding` and `saltLength` values
{
const paddingNotInteger = /^TypeError: padding must be an integer$/;
const saltLengthNotInteger = /^TypeError: saltLength must be an integer$/;
[null, undefined, NaN, 'boom', {}, [], true, false]
.forEach((invalidValue) => {
assert.throws(() => {
common.expectsError(() => {
crypto.createSign('SHA256')
.update('Test123')
.sign({
key: keyPem,
padding: invalidValue
});
}, paddingNotInteger);
}, {
code: 'ERR_INVALID_OPT_VALUE',
type: TypeError
});
assert.throws(() => {
common.expectsError(() => {
crypto.createSign('SHA256')
.update('Test123')
.sign({
@ -219,7 +219,10 @@ const modSize = 1024;
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: invalidValue
});
}, saltLengthNotInteger);
}, {
code: 'ERR_INVALID_OPT_VALUE',
type: TypeError
});
});
assert.throws(() => {
@ -234,9 +237,12 @@ const modSize = 1024;
// Test throws exception when key options is null
{
assert.throws(() => {
common.expectsError(() => {
crypto.createSign('SHA1').update('Test123').sign(null, 'base64');
}, /^Error: No key provided to sign$/);
}, {
code: 'ERR_CRYPTO_SIGN_KEY_REQUIRED',
type: Error
});
}
// RSA-PSS Sign test by verifying with 'openssl dgst -verify'

View File

@ -67,9 +67,12 @@ assert.throws(function() {
// update() should only take buffers / strings
assert.throws(function() {
crypto.createHash('sha1').update({ foo: 'bar' });
}, /^TypeError: Data must be a string or a buffer$/);
common.expectsError(
() => crypto.createHash('sha1').update({ foo: 'bar' }),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError
});
function validateList(list) {