Skip to content

Commit

Permalink
crypto: add randomFill and randomFillSync
Browse files Browse the repository at this point in the history
crypto.randomFill and crypto.randomFillSync are similar to
crypto.randomBytes, but allow passing in a buffer as the first
argument. This allows us to reuse buffers to prevent having to
create a new one on every call.

PR-URL: #10209
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
evanlucas authored and MylesBorins committed Jan 17, 2018
1 parent 7af1ad0 commit 6014d66
Show file tree
Hide file tree
Showing 5 changed files with 453 additions and 14 deletions.
62 changes: 62 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,66 @@ This should normally never take longer than a few milliseconds. The only time
when generating the random bytes may conceivably block for a longer period of
time is right after boot, when the whole system is still low on entropy.

### crypto.randomFillSync(buf[, offset][, size])
<!-- YAML
added: REPLACEME
-->

* `buf` {Buffer|Uint8Array} Must be supplied.
* `offset` {number} Defaults to `0`.
* `size` {number} Defaults to `buf.length - offset`.

Synchronous version of [`crypto.randomFill()`][].

Returns `buf`

```js
const buf = Buffer.alloc(10);
console.log(crypto.randomFillSync(buf).toString('hex'));

crypto.randomFillSync(buf, 5);
console.log(buf.toString('hex'));

// The above is equivalent to the following:
crypto.randomFillSync(buf, 5, 5);
console.log(buf.toString('hex'));
```

### crypto.randomFill(buf[, offset][, size], callback)
<!-- YAML
added: REPLACEME
-->

* `buf` {Buffer|Uint8Array} Must be supplied.
* `offset` {number} Defaults to `0`.
* `size` {number} Defaults to `buf.length - offset`.
* `callback` {Function} `function(err, buf) {}`.

This function is similar to [`crypto.randomBytes()`][] but requires the first
argument to be a [`Buffer`][] that will be filled. It also
requires that a callback is passed in.

If the `callback` function is not provided, an error will be thrown.

```js
const buf = Buffer.alloc(10);
crypto.randomFill(buf, (err, buf) => {
if (err) throw err;
console.log(buf.toString('hex'));
});

crypto.randomFill(buf, 5, (err, buf) => {
if (err) throw err;
console.log(buf.toString('hex'));
});

// The above is equivalent to the following:
crypto.randomFill(buf, 5, 5, (err, buf) => {
if (err) throw err;
console.log(buf.toString('hex'));
});
```

### crypto.setEngine(engine[, flags])
<!-- YAML
added: v0.11.11
Expand Down Expand Up @@ -2006,6 +2066,8 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`crypto.getCurves()`]: #crypto_crypto_getcurves
[`crypto.getHashes()`]: #crypto_crypto_gethashes
[`crypto.pbkdf2()`]: #crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback
[`crypto.randomBytes()`]: #crypto_crypto_randombytes_size_callback
[`crypto.randomFill()`]: #crypto_crypto_randombytesbuffer_buf_size_offset_cb
[`decipher.final()`]: #crypto_decipher_final_output_encoding
[`decipher.update()`]: #crypto_decipher_update_data_input_encoding_output_encoding
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_public_key_encoding
Expand Down
90 changes: 90 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,34 @@ 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 LazyTransform = require('internal/streams/lazy_transform');

const DH_GENERATOR = 2;

const ReflectApply = Reflect.apply;

const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
const TypedArrayProto_toStringTag =
uncurryThis(
Object.getOwnPropertyDescriptor(TypedArrayPrototype,
Symbol.toStringTag).get);

// This function is borrowed from the function with the same name on V8 Extras'
// `utils` object. V8 implements Reflect.apply very efficiently in conjunction
// with the spread syntax, such that no additional special case is needed for
// function calls w/o arguments.
// Refs: https://github.com/v8/v8/blob/d6ead37d265d7215cf9c5f768f279e21bd170212/src/js/prologue.js#L152-L156
function uncurryThis(func) {
return (thisArg, ...args) => ReflectApply(func, thisArg, args);
}

function isUint8Array(value) {
return TypedArrayProto_toStringTag(value) === 'Uint8Array';
}

Object.defineProperty(exports, 'constants', {
configurable: false,
enumerable: true,
Expand Down Expand Up @@ -677,6 +699,74 @@ exports.setEngine = function setEngine(id, flags) {
return binding.setEngine(id, flags);
};

const kMaxUint32 = Math.pow(2, 32) - 1;

function randomFillSync(buf, offset = 0, size) {
if (!isUint8Array(buf)) {
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
}

assertOffset(offset, buf.length);

if (size === undefined) size = buf.length - offset;

assertSize(size, offset, buf.length);

return binding.randomFill(buf, offset, size);
}
exports.randomFillSync = randomFillSync;

function randomFill(buf, offset, size, cb) {
if (!isUint8Array(buf)) {
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
}

if (typeof offset === 'function') {
cb = offset;
offset = 0;
size = buf.length;
} else if (typeof size === 'function') {
cb = size;
size = buf.length - offset;
} else if (typeof cb !== 'function') {
throw new TypeError('"cb" argument must be a function');
}

assertOffset(offset, buf.length);
assertSize(size, offset, buf.length);

return binding.randomFill(buf, offset, size, cb);
}
exports.randomFill = randomFill;

function assertOffset(offset, length) {
if (typeof offset !== 'number' || offset !== offset) {
throw new TypeError('offset must be a number');
}

if (offset > kMaxUint32 || offset < 0) {
throw new TypeError('offset must be a uint32');
}

if (offset > kBufferMaxLength || offset > length) {
throw new RangeError('offset out of range');
}
}

function assertSize(size, offset, length) {
if (typeof size !== 'number' || size !== size) {
throw new TypeError('size must be a number');
}

if (size > kMaxUint32 || size < 0) {
throw new TypeError('size must be a uint32');
}

if (size + offset > length || size > kBufferMaxLength) {
throw new RangeError('buffer too small');
}
}

exports.randomBytes = exports.pseudoRandomBytes = randomBytes;

exports.rng = exports.prng = randomBytes;
Expand Down
111 changes: 97 additions & 14 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5595,11 +5595,18 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
// Only instantiate within a valid HandleScope.
class RandomBytesRequest : public AsyncWrap {
public:
RandomBytesRequest(Environment* env, Local<Object> object, size_t size)
enum FreeMode { FREE_DATA, DONT_FREE_DATA };

RandomBytesRequest(Environment* env,
Local<Object> object,
size_t size,
char* data,
FreeMode free_mode)
: AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO),
error_(0),
size_(size),
data_(node::Malloc(size)) {
data_(data),
free_mode_(free_mode) {
Wrap(object, this);
}

Expand All @@ -5620,9 +5627,15 @@ class RandomBytesRequest : public AsyncWrap {
return data_;
}

inline void set_data(char* data) {
data_ = data;
}

inline void release() {
free(data_);
size_ = 0;
if (free_mode_ == FREE_DATA) {
free(data_);
}
}

inline void return_memory(char** d, size_t* len) {
Expand All @@ -5648,6 +5661,7 @@ class RandomBytesRequest : public AsyncWrap {
unsigned long error_; // NOLINT(runtime/int)
size_t size_;
char* data_;
const FreeMode free_mode_;
};


Expand Down Expand Up @@ -5686,7 +5700,18 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
size_t size;
req->return_memory(&data, &size);
argv[0] = Null(req->env()->isolate());
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
Local<Value> buffer =
req->object()->Get(req->env()->context(),
req->env()->buffer_string()).ToLocalChecked();

if (buffer->IsUint8Array()) {
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();
}
}
}

Expand All @@ -5705,11 +5730,22 @@ void RandomBytesAfter(uv_work_t* work_req, int status) {
}


void RandomBytesProcessSync(Environment* env,
RandomBytesRequest* req,
Local<Value> argv[2]) {
env->PrintSyncTrace();
RandomBytesWork(req->work_req());
RandomBytesCheck(req, argv);
delete req;

if (!argv[0]->IsNull())
env->isolate()->ThrowException(argv[0]);
}


void RandomBytes(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

// maybe allow a buffer to write to? cuts down on object creation
// when generating random data in a loop
if (!args[0]->IsUint32()) {
return env->ThrowTypeError("size must be a number >= 0");
}
Expand All @@ -5719,7 +5755,13 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
return env->ThrowRangeError("size is not a valid Smi");

Local<Object> obj = env->NewInternalFieldObject();
RandomBytesRequest* req = new RandomBytesRequest(env, obj, size);
char* data = node::Malloc(size);
RandomBytesRequest* req =
new RandomBytesRequest(env,
obj,
size,
data,
RandomBytesRequest::FREE_DATA);

if (args[1]->IsFunction()) {
obj->Set(FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"), args[1]);
Expand All @@ -5732,15 +5774,55 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
RandomBytesAfter);
args.GetReturnValue().Set(obj);
} else {
env->PrintSyncTrace();
Local<Value> argv[2];
RandomBytesWork(req->work_req());
RandomBytesCheck(req, argv);
delete req;
RandomBytesProcessSync(env, req, argv);
if (argv[0]->IsNull())
args.GetReturnValue().Set(argv[1]);
}
}

if (!argv[0]->IsNull())
env->isolate()->ThrowException(argv[0]);
else

void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CHECK(args[0]->IsUint8Array());
CHECK(args[1]->IsUint32());
CHECK(args[2]->IsUint32());

int64_t offset = args[1]->IntegerValue();
int64_t size = args[2]->IntegerValue();

Local<Object> obj = env->NewInternalFieldObject();
obj->Set(env->context(), env->buffer_string(), args[0]).FromJust();
char* data = Buffer::Data(args[0]);
data += offset;

RandomBytesRequest* req =
new RandomBytesRequest(env,
obj,
size,
data,
RandomBytesRequest::DONT_FREE_DATA);
if (args[3]->IsFunction()) {
obj->Set(env->context(),
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"),
args[3]).FromJust();

if (env->in_domain()) {
obj->Set(env->context(),
env->domain_string(),
env->domain_array()->Get(0)).FromJust();
}

uv_queue_work(env->event_loop(),
req->work_req(),
RandomBytesWork,
RandomBytesAfter);
args.GetReturnValue().Set(obj);
} else {
Local<Value> argv[2];
RandomBytesProcessSync(env, req, argv);
if (argv[0]->IsNull())
args.GetReturnValue().Set(argv[1]);
}
}
Expand Down Expand Up @@ -6168,6 +6250,7 @@ void InitCrypto(Local<Object> target,
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
env->SetMethod(target, "PBKDF2", PBKDF2);
env->SetMethod(target, "randomBytes", RandomBytes);
env->SetMethod(target, "randomFill", RandomBytesBuffer);
env->SetMethod(target, "timingSafeEqual", TimingSafeEqual);
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
env->SetMethod(target, "getCiphers", GetCiphers);
Expand Down
Loading

0 comments on commit 6014d66

Please sign in to comment.