Skip to content

Commit 6f62f83

Browse files
evanlucasMylesBorins
authored andcommitted
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 7af1ad0 commit 6f62f83

File tree

6 files changed

+437
-14
lines changed

6 files changed

+437
-14
lines changed

doc/api/crypto.md

+62
Original file line numberDiff line numberDiff line change
@@ -1588,6 +1588,66 @@ This should normally never take longer than a few milliseconds. The only time
15881588
when generating the random bytes may conceivably block for a longer period of
15891589
time is right after boot, when the whole system is still low on entropy.
15901590

1591+
### crypto.randomFillSync(buf[, offset][, size])
1592+
<!-- YAML
1593+
added: REPLACEME
1594+
-->
1595+
1596+
* `buf` {Buffer|Uint8Array} Must be supplied.
1597+
* `offset` {number} Defaults to `0`.
1598+
* `size` {number} Defaults to `buf.length - offset`.
1599+
1600+
Synchronous version of [`crypto.randomFill()`][].
1601+
1602+
Returns `buf`
1603+
1604+
```js
1605+
const buf = Buffer.alloc(10);
1606+
console.log(crypto.randomFillSync(buf).toString('hex'));
1607+
1608+
crypto.randomFillSync(buf, 5);
1609+
console.log(buf.toString('hex'));
1610+
1611+
// The above is equivalent to the following:
1612+
crypto.randomFillSync(buf, 5, 5);
1613+
console.log(buf.toString('hex'));
1614+
```
1615+
1616+
### crypto.randomFill(buf[, offset][, size], callback)
1617+
<!-- YAML
1618+
added: REPLACEME
1619+
-->
1620+
1621+
* `buf` {Buffer|Uint8Array} Must be supplied.
1622+
* `offset` {number} Defaults to `0`.
1623+
* `size` {number} Defaults to `buf.length - offset`.
1624+
* `callback` {Function} `function(err, buf) {}`.
1625+
1626+
This function is similar to [`crypto.randomBytes()`][] but requires the first
1627+
argument to be a [`Buffer`][] that will be filled. It also
1628+
requires that a callback is passed in.
1629+
1630+
If the `callback` function is not provided, an error will be thrown.
1631+
1632+
```js
1633+
const buf = Buffer.alloc(10);
1634+
crypto.randomFill(buf, (err, buf) => {
1635+
if (err) throw err;
1636+
console.log(buf.toString('hex'));
1637+
});
1638+
1639+
crypto.randomFill(buf, 5, (err, buf) => {
1640+
if (err) throw err;
1641+
console.log(buf.toString('hex'));
1642+
});
1643+
1644+
// The above is equivalent to the following:
1645+
crypto.randomFill(buf, 5, 5, (err, buf) => {
1646+
if (err) throw err;
1647+
console.log(buf.toString('hex'));
1648+
});
1649+
```
1650+
15911651
### crypto.setEngine(engine[, flags])
15921652
<!-- YAML
15931653
added: v0.11.11
@@ -2006,6 +2066,8 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
20062066
[`crypto.getCurves()`]: #crypto_crypto_getcurves
20072067
[`crypto.getHashes()`]: #crypto_crypto_gethashes
20082068
[`crypto.pbkdf2()`]: #crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback
2069+
[`crypto.randomBytes()`]: #crypto_crypto_randombytes_size_callback
2070+
[`crypto.randomFill()`]: #crypto_crypto_randombytesbuffer_buf_size_offset_cb
20092071
[`decipher.final()`]: #crypto_decipher_final_output_encoding
20102072
[`decipher.update()`]: #crypto_decipher_update_data_input_encoding_output_encoding
20112073
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_public_key_encoding

lib/crypto.js

+70
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ const setFipsCrypto = binding.setFipsCrypto;
1919
const timingSafeEqual = binding.timingSafeEqual;
2020

2121
const Buffer = require('buffer').Buffer;
22+
const kBufferMaxLength = require('buffer').kMaxLength;
2223
const stream = require('stream');
2324
const util = require('util');
2425
const LazyTransform = require('internal/streams/lazy_transform');
26+
const isArrayBufferView = internalUtil.isArrayBufferView;
2527

2628
const DH_GENERATOR = 2;
2729

@@ -677,6 +679,74 @@ exports.setEngine = function setEngine(id, flags) {
677679
return binding.setEngine(id, flags);
678680
};
679681

682+
const kMaxUint32 = Math.pow(2, 32) - 1;
683+
684+
function randomFillSync(buf, offset = 0, size) {
685+
if (!isArrayBufferView(buf)) {
686+
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
687+
}
688+
689+
assertOffset(offset, buf.length);
690+
691+
if (size === undefined) size = buf.length - offset;
692+
693+
assertSize(size, offset, buf.length);
694+
695+
return binding.randomFill(buf, offset, size);
696+
}
697+
exports.randomFillSync = randomFillSync;
698+
699+
function randomFill(buf, offset, size, cb) {
700+
if (!isArrayBufferView(buf)) {
701+
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
702+
}
703+
704+
if (typeof offset === 'function') {
705+
cb = offset;
706+
offset = 0;
707+
size = buf.length;
708+
} else if (typeof size === 'function') {
709+
cb = size;
710+
size = buf.length - offset;
711+
} else if (typeof cb !== 'function') {
712+
throw new TypeError('"cb" argument must be a function');
713+
}
714+
715+
assertOffset(offset, buf.length);
716+
assertSize(size, offset, buf.length);
717+
718+
return binding.randomFill(buf, offset, size, cb);
719+
}
720+
exports.randomFill = randomFill;
721+
722+
function assertOffset(offset, length) {
723+
if (typeof offset !== 'number' || offset !== offset) {
724+
throw new TypeError('offset must be a number');
725+
}
726+
727+
if (offset > kMaxUint32 || offset < 0) {
728+
throw new TypeError('offset must be a uint32');
729+
}
730+
731+
if (offset > kBufferMaxLength || offset > length) {
732+
throw new RangeError('offset out of range');
733+
}
734+
}
735+
736+
function assertSize(size, offset, length) {
737+
if (typeof size !== 'number' || size !== size) {
738+
throw new TypeError('size must be a number');
739+
}
740+
741+
if (size > kMaxUint32 || size < 0) {
742+
throw new TypeError('size must be a uint32');
743+
}
744+
745+
if (size + offset > length || size > kBufferMaxLength) {
746+
throw new RangeError('buffer too small');
747+
}
748+
}
749+
680750
exports.randomBytes = exports.pseudoRandomBytes = randomBytes;
681751

682752
exports.rng = exports.prng = randomBytes;

lib/internal/util.js

+4
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,7 @@ exports.toLength = function toLength(argument) {
183183
const len = toInteger(argument);
184184
return len <= 0 ? 0 : Math.min(len, Number.MAX_SAFE_INTEGER);
185185
};
186+
187+
// Cached to make sure no userland code can tamper with it.
188+
const isArrayBufferView = ArrayBuffer.isView;
189+
exports.isArrayBufferView = isArrayBufferView;

src/node_crypto.cc

+97-14
Original file line numberDiff line numberDiff line change
@@ -5595,11 +5595,18 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
55955595
// Only instantiate within a valid HandleScope.
55965596
class RandomBytesRequest : public AsyncWrap {
55975597
public:
5598-
RandomBytesRequest(Environment* env, Local<Object> object, size_t size)
5598+
enum FreeMode { FREE_DATA, DONT_FREE_DATA };
5599+
5600+
RandomBytesRequest(Environment* env,
5601+
Local<Object> object,
5602+
size_t size,
5603+
char* data,
5604+
FreeMode free_mode)
55995605
: AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO),
56005606
error_(0),
56015607
size_(size),
5602-
data_(node::Malloc(size)) {
5608+
data_(data),
5609+
free_mode_(free_mode) {
56035610
Wrap(object, this);
56045611
}
56055612

@@ -5620,9 +5627,15 @@ class RandomBytesRequest : public AsyncWrap {
56205627
return data_;
56215628
}
56225629

5630+
inline void set_data(char* data) {
5631+
data_ = data;
5632+
}
5633+
56235634
inline void release() {
5624-
free(data_);
56255635
size_ = 0;
5636+
if (free_mode_ == FREE_DATA) {
5637+
free(data_);
5638+
}
56265639
}
56275640

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

56535667

@@ -5686,7 +5700,18 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
56865700
size_t size;
56875701
req->return_memory(&data, &size);
56885702
argv[0] = Null(req->env()->isolate());
5689-
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
5703+
Local<Value> buffer =
5704+
req->object()->Get(req->env()->context(),
5705+
req->env()->buffer_string()).ToLocalChecked();
5706+
5707+
if (buffer->IsUint8Array()) {
5708+
CHECK_LE(req->size(), Buffer::Length(buffer));
5709+
char* buf = Buffer::Data(buffer);
5710+
memcpy(buf, data, req->size());
5711+
argv[1] = buffer;
5712+
} else {
5713+
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
5714+
}
56905715
}
56915716
}
56925717

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

57075732

5733+
void RandomBytesProcessSync(Environment* env,
5734+
RandomBytesRequest* req,
5735+
Local<Value> argv[2]) {
5736+
env->PrintSyncTrace();
5737+
RandomBytesWork(req->work_req());
5738+
RandomBytesCheck(req, argv);
5739+
delete req;
5740+
5741+
if (!argv[0]->IsNull())
5742+
env->isolate()->ThrowException(argv[0]);
5743+
}
5744+
5745+
57085746
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
57095747
Environment* env = Environment::GetCurrent(args);
57105748

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

57215757
Local<Object> obj = env->NewInternalFieldObject();
5722-
RandomBytesRequest* req = new RandomBytesRequest(env, obj, size);
5758+
char* data = node::Malloc(size);
5759+
RandomBytesRequest* req =
5760+
new RandomBytesRequest(env,
5761+
obj,
5762+
size,
5763+
data,
5764+
RandomBytesRequest::FREE_DATA);
57235765

57245766
if (args[1]->IsFunction()) {
57255767
obj->Set(FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"), args[1]);
@@ -5732,15 +5774,55 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
57325774
RandomBytesAfter);
57335775
args.GetReturnValue().Set(obj);
57345776
} else {
5735-
env->PrintSyncTrace();
57365777
Local<Value> argv[2];
5737-
RandomBytesWork(req->work_req());
5738-
RandomBytesCheck(req, argv);
5739-
delete req;
5778+
RandomBytesProcessSync(env, req, argv);
5779+
if (argv[0]->IsNull())
5780+
args.GetReturnValue().Set(argv[1]);
5781+
}
5782+
}
57405783

5741-
if (!argv[0]->IsNull())
5742-
env->isolate()->ThrowException(argv[0]);
5743-
else
5784+
5785+
void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
5786+
Environment* env = Environment::GetCurrent(args);
5787+
5788+
CHECK(args[0]->IsUint8Array());
5789+
CHECK(args[1]->IsUint32());
5790+
CHECK(args[2]->IsUint32());
5791+
5792+
int64_t offset = args[1]->IntegerValue();
5793+
int64_t size = args[2]->IntegerValue();
5794+
5795+
Local<Object> obj = env->NewInternalFieldObject();
5796+
obj->Set(env->context(), env->buffer_string(), args[0]).FromJust();
5797+
char* data = Buffer::Data(args[0]);
5798+
data += offset;
5799+
5800+
RandomBytesRequest* req =
5801+
new RandomBytesRequest(env,
5802+
obj,
5803+
size,
5804+
data,
5805+
RandomBytesRequest::DONT_FREE_DATA);
5806+
if (args[3]->IsFunction()) {
5807+
obj->Set(env->context(),
5808+
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"),
5809+
args[3]).FromJust();
5810+
5811+
if (env->in_domain()) {
5812+
obj->Set(env->context(),
5813+
env->domain_string(),
5814+
env->domain_array()->Get(0)).FromJust();
5815+
}
5816+
5817+
uv_queue_work(env->event_loop(),
5818+
req->work_req(),
5819+
RandomBytesWork,
5820+
RandomBytesAfter);
5821+
args.GetReturnValue().Set(obj);
5822+
} else {
5823+
Local<Value> argv[2];
5824+
RandomBytesProcessSync(env, req, argv);
5825+
if (argv[0]->IsNull())
57445826
args.GetReturnValue().Set(argv[1]);
57455827
}
57465828
}
@@ -6168,6 +6250,7 @@ void InitCrypto(Local<Object> target,
61686250
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
61696251
env->SetMethod(target, "PBKDF2", PBKDF2);
61706252
env->SetMethod(target, "randomBytes", RandomBytes);
6253+
env->SetMethod(target, "randomFill", RandomBytesBuffer);
61716254
env->SetMethod(target, "timingSafeEqual", TimingSafeEqual);
61726255
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
61736256
env->SetMethod(target, "getCiphers", GetCiphers);

0 commit comments

Comments
 (0)