Skip to content

Commit d06eb53

Browse files
committed
crypto: add randomFill and randomFillSync
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 <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent ade80ee commit d06eb53

File tree

5 files changed

+433
-14
lines changed

5 files changed

+433
-14
lines changed

doc/api/crypto.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,66 @@ This should normally never take longer than a few milliseconds. The only time
17131713
when generating the random bytes may conceivably block for a longer period of
17141714
time is right after boot, when the whole system is still low on entropy.
17151715

1716+
### crypto.randomFillSync(buf[, offset][, size])
1717+
<!-- YAML
1718+
added: REPLACEME
1719+
-->
1720+
1721+
* `buf` {Buffer|Uint8Array} Must be supplied.
1722+
* `offset` {number} Defaults to `0`.
1723+
* `size` {number} Defaults to `buf.length - offset`.
1724+
1725+
Synchronous version of [`crypto.randomFill()`][].
1726+
1727+
Returns `buf`
1728+
1729+
```js
1730+
const buf = Buffer.alloc(10);
1731+
console.log(crypto.randomFillSync(buf).toString('hex'));
1732+
1733+
crypto.randomFillSync(buf, 5);
1734+
console.log(buf.toString('hex'));
1735+
1736+
// The above is equivalent to the following:
1737+
crypto.randomFillSync(buf, 5, 5);
1738+
console.log(buf.toString('hex'));
1739+
```
1740+
1741+
### crypto.randomFill(buf[, offset][, size], callback)
1742+
<!-- YAML
1743+
added: REPLACEME
1744+
-->
1745+
1746+
* `buf` {Buffer|Uint8Array} Must be supplied.
1747+
* `offset` {number} Defaults to `0`.
1748+
* `size` {number} Defaults to `buf.length - offset`.
1749+
* `callback` {Function} `function(err, buf) {}`.
1750+
1751+
This function is similar to [`crypto.randomBytes()`][] but requires the first
1752+
argument to be a [`Buffer`][] that will be filled. It also
1753+
requires that a callback is passed in.
1754+
1755+
If the `callback` function is not provided, an error will be thrown.
1756+
1757+
```js
1758+
const buf = Buffer.alloc(10);
1759+
crypto.randomFill(buf, (err, buf) => {
1760+
if (err) throw err;
1761+
console.log(buf.toString('hex'));
1762+
});
1763+
1764+
crypto.randomFill(buf, 5, (err, buf) => {
1765+
if (err) throw err;
1766+
console.log(buf.toString('hex'));
1767+
});
1768+
1769+
// The above is equivalent to the following:
1770+
crypto.randomFill(buf, 5, 5, (err, buf) => {
1771+
if (err) throw err;
1772+
console.log(buf.toString('hex'));
1773+
});
1774+
```
1775+
17161776
### crypto.setEngine(engine[, flags])
17171777
<!-- YAML
17181778
added: v0.11.11
@@ -2152,6 +2212,8 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
21522212
[`crypto.getCurves()`]: #crypto_crypto_getcurves
21532213
[`crypto.getHashes()`]: #crypto_crypto_gethashes
21542214
[`crypto.pbkdf2()`]: #crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback
2215+
[`crypto.randomBytes()`]: #crypto_crypto_randombytes_size_callback
2216+
[`crypto.randomFill()`]: #crypto_crypto_randombytesbuffer_buf_size_offset_cb
21552217
[`decipher.final()`]: #crypto_decipher_final_output_encoding
21562218
[`decipher.update()`]: #crypto_decipher_update_data_input_encoding_output_encoding
21572219
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_public_key_encoding

lib/crypto.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ const setFipsCrypto = binding.setFipsCrypto;
4040
const timingSafeEqual = binding.timingSafeEqual;
4141

4242
const Buffer = require('buffer').Buffer;
43+
const kBufferMaxLength = require('buffer').kMaxLength;
4344
const stream = require('stream');
4445
const util = require('util');
46+
const { isUint8Array } = process.binding('util');
4547
const LazyTransform = require('internal/streams/lazy_transform');
4648

4749
const DH_GENERATOR = 2;
@@ -696,6 +698,74 @@ exports.setEngine = function setEngine(id, flags) {
696698
return binding.setEngine(id, flags);
697699
};
698700

701+
const kMaxUint32 = Math.pow(2, 32) - 1;
702+
703+
function randomFillSync(buf, offset = 0, size) {
704+
if (!isUint8Array(buf)) {
705+
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
706+
}
707+
708+
assertOffset(offset, buf.length);
709+
710+
if (size === undefined) size = buf.length - offset;
711+
712+
assertSize(size, offset, buf.length);
713+
714+
return binding.randomFill(buf, offset, size);
715+
}
716+
exports.randomFillSync = randomFillSync;
717+
718+
function randomFill(buf, offset, size, cb) {
719+
if (!isUint8Array(buf)) {
720+
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
721+
}
722+
723+
if (typeof offset === 'function') {
724+
cb = offset;
725+
offset = 0;
726+
size = buf.length;
727+
} else if (typeof size === 'function') {
728+
cb = size;
729+
size = buf.length - offset;
730+
} else if (typeof cb !== 'function') {
731+
throw new TypeError('"cb" argument must be a function');
732+
}
733+
734+
assertOffset(offset, buf.length);
735+
assertSize(size, offset, buf.length);
736+
737+
return binding.randomFill(buf, offset, size, cb);
738+
}
739+
exports.randomFill = randomFill;
740+
741+
function assertOffset(offset, length) {
742+
if (typeof offset !== 'number' || offset !== offset) {
743+
throw new TypeError('offset must be a number');
744+
}
745+
746+
if (offset > kMaxUint32 || offset < 0) {
747+
throw new TypeError('offset must be a uint32');
748+
}
749+
750+
if (offset > kBufferMaxLength || offset > length) {
751+
throw new RangeError('offset out of range');
752+
}
753+
}
754+
755+
function assertSize(size, offset, length) {
756+
if (typeof size !== 'number' || size !== size) {
757+
throw new TypeError('size must be a number');
758+
}
759+
760+
if (size > kMaxUint32 || size < 0) {
761+
throw new TypeError('size must be a uint32');
762+
}
763+
764+
if (size + offset > length || size > kBufferMaxLength) {
765+
throw new RangeError('buffer too small');
766+
}
767+
}
768+
699769
exports.randomBytes = exports.pseudoRandomBytes = randomBytes;
700770

701771
exports.rng = exports.prng = randomBytes;

src/node_crypto.cc

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5574,11 +5574,18 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
55745574
// Only instantiate within a valid HandleScope.
55755575
class RandomBytesRequest : public AsyncWrap {
55765576
public:
5577-
RandomBytesRequest(Environment* env, Local<Object> object, size_t size)
5577+
enum FreeMode { FREE_DATA, DONT_FREE_DATA };
5578+
5579+
RandomBytesRequest(Environment* env,
5580+
Local<Object> object,
5581+
size_t size,
5582+
char* data,
5583+
FreeMode free_mode)
55785584
: AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO),
55795585
error_(0),
55805586
size_(size),
5581-
data_(node::Malloc(size)) {
5587+
data_(data),
5588+
free_mode_(free_mode) {
55825589
Wrap(object, this);
55835590
}
55845591

@@ -5599,9 +5606,15 @@ class RandomBytesRequest : public AsyncWrap {
55995606
return data_;
56005607
}
56015608

5609+
inline void set_data(char* data) {
5610+
data_ = data;
5611+
}
5612+
56025613
inline void release() {
5603-
free(data_);
56045614
size_ = 0;
5615+
if (free_mode_ == FREE_DATA) {
5616+
free(data_);
5617+
}
56055618
}
56065619

56075620
inline void return_memory(char** d, size_t* len) {
@@ -5627,6 +5640,7 @@ class RandomBytesRequest : public AsyncWrap {
56275640
unsigned long error_; // NOLINT(runtime/int)
56285641
size_t size_;
56295642
char* data_;
5643+
const FreeMode free_mode_;
56305644
};
56315645

56325646

@@ -5665,7 +5679,18 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
56655679
size_t size;
56665680
req->return_memory(&data, &size);
56675681
argv[0] = Null(req->env()->isolate());
5668-
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
5682+
Local<Value> buffer =
5683+
req->object()->Get(req->env()->context(),
5684+
req->env()->buffer_string()).ToLocalChecked();
5685+
5686+
if (buffer->IsUint8Array()) {
5687+
CHECK_LE(req->size(), Buffer::Length(buffer));
5688+
char* buf = Buffer::Data(buffer);
5689+
memcpy(buf, data, req->size());
5690+
argv[1] = buffer;
5691+
} else {
5692+
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
5693+
}
56695694
}
56705695
}
56715696

@@ -5684,11 +5709,22 @@ void RandomBytesAfter(uv_work_t* work_req, int status) {
56845709
}
56855710

56865711

5712+
void RandomBytesProcessSync(Environment* env,
5713+
RandomBytesRequest* req,
5714+
Local<Value> argv[2]) {
5715+
env->PrintSyncTrace();
5716+
RandomBytesWork(req->work_req());
5717+
RandomBytesCheck(req, argv);
5718+
delete req;
5719+
5720+
if (!argv[0]->IsNull())
5721+
env->isolate()->ThrowException(argv[0]);
5722+
}
5723+
5724+
56875725
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
56885726
Environment* env = Environment::GetCurrent(args);
56895727

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

57005736
Local<Object> obj = env->NewInternalFieldObject();
5701-
RandomBytesRequest* req = new RandomBytesRequest(env, obj, size);
5737+
char* data = node::Malloc(size);
5738+
RandomBytesRequest* req =
5739+
new RandomBytesRequest(env,
5740+
obj,
5741+
size,
5742+
data,
5743+
RandomBytesRequest::FREE_DATA);
57025744

57035745
if (args[1]->IsFunction()) {
57045746
obj->Set(env->ondone_string(), args[1]);
@@ -5711,15 +5753,55 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
57115753
RandomBytesAfter);
57125754
args.GetReturnValue().Set(obj);
57135755
} else {
5714-
env->PrintSyncTrace();
57155756
Local<Value> argv[2];
5716-
RandomBytesWork(req->work_req());
5717-
RandomBytesCheck(req, argv);
5718-
delete req;
5757+
RandomBytesProcessSync(env, req, argv);
5758+
if (argv[0]->IsNull())
5759+
args.GetReturnValue().Set(argv[1]);
5760+
}
5761+
}
57195762

5720-
if (!argv[0]->IsNull())
5721-
env->isolate()->ThrowException(argv[0]);
5722-
else
5763+
5764+
void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
5765+
Environment* env = Environment::GetCurrent(args);
5766+
5767+
CHECK(args[0]->IsUint8Array());
5768+
CHECK(args[1]->IsUint32());
5769+
CHECK(args[2]->IsUint32());
5770+
5771+
int64_t offset = args[1]->IntegerValue();
5772+
int64_t size = args[2]->IntegerValue();
5773+
5774+
Local<Object> obj = env->NewInternalFieldObject();
5775+
obj->Set(env->context(), env->buffer_string(), args[0]).FromJust();
5776+
char* data = Buffer::Data(args[0]);
5777+
data += offset;
5778+
5779+
RandomBytesRequest* req =
5780+
new RandomBytesRequest(env,
5781+
obj,
5782+
size,
5783+
data,
5784+
RandomBytesRequest::DONT_FREE_DATA);
5785+
if (args[3]->IsFunction()) {
5786+
obj->Set(env->context(),
5787+
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"),
5788+
args[3]).FromJust();
5789+
5790+
if (env->in_domain()) {
5791+
obj->Set(env->context(),
5792+
env->domain_string(),
5793+
env->domain_array()->Get(0)).FromJust();
5794+
}
5795+
5796+
uv_queue_work(env->event_loop(),
5797+
req->work_req(),
5798+
RandomBytesWork,
5799+
RandomBytesAfter);
5800+
args.GetReturnValue().Set(obj);
5801+
} else {
5802+
Local<Value> argv[2];
5803+
RandomBytesProcessSync(env, req, argv);
5804+
if (argv[0]->IsNull())
57235805
args.GetReturnValue().Set(argv[1]);
57245806
}
57255807
}
@@ -6144,6 +6226,7 @@ void InitCrypto(Local<Object> target,
61446226
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
61456227
env->SetMethod(target, "PBKDF2", PBKDF2);
61466228
env->SetMethod(target, "randomBytes", RandomBytes);
6229+
env->SetMethod(target, "randomFill", RandomBytesBuffer);
61476230
env->SetMethod(target, "timingSafeEqual", TimingSafeEqual);
61486231
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
61496232
env->SetMethod(target, "getCiphers", GetCiphers);

0 commit comments

Comments
 (0)