Skip to content

Commit

Permalink
crypto: refactor the crypto module
Browse files Browse the repository at this point in the history
* 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: nodejs#15231
Reviewed-By: Michaël Zasso <[email protected]>
Reviewed-By: Fedor Indutny <[email protected]>
jasnell committed Sep 18, 2017
1 parent 8fa5fcc commit c75f87c
Showing 25 changed files with 1,712 additions and 1,030 deletions.
108 changes: 102 additions & 6 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
@@ -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

906 changes: 132 additions & 774 deletions lib/crypto.js

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions lib/internal/crypto/certificate.js
Original file line number Diff line number Diff line change
@@ -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;
214 changes: 214 additions & 0 deletions lib/internal/crypto/cipher.js
Original file line number Diff line number Diff line change
@@ -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,
};
216 changes: 216 additions & 0 deletions lib/internal/crypto/diffiehellman.js
Original file line number Diff line number Diff line change
@@ -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 changes: 125 additions & 0 deletions lib/internal/crypto/hash.js
Original file line number Diff line number Diff line change
@@ -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
};
59 changes: 59 additions & 0 deletions lib/internal/crypto/pbkdf2.js
Original file line number Diff line number Diff line change
@@ -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
};
98 changes: 98 additions & 0 deletions lib/internal/crypto/random.js
Original file line number Diff line number Diff line change
@@ -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 changes: 131 additions & 0 deletions lib/internal/crypto/sig.js
Original file line number Diff line number Diff line change
@@ -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
};
70 changes: 70 additions & 0 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
@@ -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
};
6 changes: 6 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
@@ -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');
8 changes: 8 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
@@ -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',
73 changes: 17 additions & 56 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
@@ -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());

1 change: 1 addition & 0 deletions src/node_util.cc
Original file line number Diff line number Diff line change
@@ -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) \
9 changes: 6 additions & 3 deletions test/parallel/test-crypto-binary-default.js
Original file line number Diff line number Diff line change
@@ -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,
50 changes: 36 additions & 14 deletions test/parallel/test-crypto-certificate.js
Original file line number Diff line number Diff line change
@@ -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);
55 changes: 31 additions & 24 deletions test/parallel/test-crypto-dh.js
Original file line number Diff line number Diff line change
@@ -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'
});
21 changes: 14 additions & 7 deletions test/parallel/test-crypto-engine.js
Original file line number Diff line number Diff line change
@@ -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'
});
38 changes: 29 additions & 9 deletions test/parallel/test-crypto-hash.js
Original file line number Diff line number Diff line change
@@ -105,14 +105,34 @@ assert.notStrictEqual(

const h3 = crypto.createHash('sha256');
h3.digest();
assert.throws(function() {
h3.digest();
}, /Digest already called/);

assert.throws(function() {
h3.update('foo');
}, /Digest already called/);
common.expectsError(
() => h3.digest(),
{
code: 'ERR_CRYPTO_HASH_FINALIZED',
type: Error
});

common.expectsError(
() => h3.update('foo'),
{
code: 'ERR_CRYPTO_HASH_FINALIZED',
type: Error
});

common.expectsError(
() => crypto.createHash('sha256').digest('ucs2'),
{
code: 'ERR_CRYPTO_HASH_DIGEST_NO_UTF16',
type: Error
}
);

assert.throws(function() {
crypto.createHash('sha256').digest('ucs2');
}, /^Error: hash\.digest\(\) does not support UTF-16$/);
common.expectsError(
() => crypto.createHash(),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "algorithm" argument must be of type string'
}
);
23 changes: 9 additions & 14 deletions test/parallel/test-crypto-hmac.js
Original file line number Diff line number Diff line change
@@ -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().
{
30 changes: 21 additions & 9 deletions test/parallel/test-crypto-pbkdf2.js
Original file line number Diff line number Diff line change
@@ -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'
});
422 changes: 320 additions & 102 deletions test/parallel/test-crypto-random.js

Large diffs are not rendered by default.

24 changes: 15 additions & 9 deletions test/parallel/test-crypto-sign-verify.js
Original file line number Diff line number Diff line change
@@ -197,29 +197,32 @@ 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({
key: keyPem,
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'
9 changes: 6 additions & 3 deletions test/parallel/test-crypto.js
Original file line number Diff line number Diff line change
@@ -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) {

0 comments on commit c75f87c

Please sign in to comment.