Skip to content

Commit 75f4329

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 513fc62 commit 75f4329

File tree

5 files changed

+435
-14
lines changed

5 files changed

+435
-14
lines changed

doc/api/crypto.md

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

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

lib/crypto.js

+70
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ 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');
25+
const { isUint8Array } = process.binding('util');
2426
const LazyTransform = require('internal/streams/lazy_transform');
2527

2628
const DH_GENERATOR = 2;
@@ -634,6 +636,74 @@ exports.setEngine = function setEngine(id, flags) {
634636
return binding.setEngine(id, flags);
635637
};
636638

639+
const kMaxUint32 = Math.pow(2, 32) - 1;
640+
641+
function randomFillSync(buf, offset = 0, size) {
642+
if (!isUint8Array(buf)) {
643+
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
644+
}
645+
646+
assertOffset(offset, buf.length);
647+
648+
if (size === undefined) size = buf.length - offset;
649+
650+
assertSize(size, offset, buf.length);
651+
652+
return binding.randomFill(buf, offset, size);
653+
}
654+
exports.randomFillSync = randomFillSync;
655+
656+
function randomFill(buf, offset, size, cb) {
657+
if (!isUint8Array(buf)) {
658+
throw new TypeError('"buf" argument must be a Buffer or Uint8Array');
659+
}
660+
661+
if (typeof offset === 'function') {
662+
cb = offset;
663+
offset = 0;
664+
size = buf.length;
665+
} else if (typeof size === 'function') {
666+
cb = size;
667+
size = buf.length - offset;
668+
} else if (typeof cb !== 'function') {
669+
throw new TypeError('"cb" argument must be a function');
670+
}
671+
672+
assertOffset(offset, buf.length);
673+
assertSize(size, offset, buf.length);
674+
675+
return binding.randomFill(buf, offset, size, cb);
676+
}
677+
exports.randomFill = randomFill;
678+
679+
function assertOffset(offset, length) {
680+
if (typeof offset !== 'number' || offset !== offset) {
681+
throw new TypeError('offset must be a number');
682+
}
683+
684+
if (offset > kMaxUint32 || offset < 0) {
685+
throw new TypeError('offset must be a uint32');
686+
}
687+
688+
if (offset > kBufferMaxLength || offset > length) {
689+
throw new RangeError('offset out of range');
690+
}
691+
}
692+
693+
function assertSize(size, offset, length) {
694+
if (typeof size !== 'number' || size !== size) {
695+
throw new TypeError('size must be a number');
696+
}
697+
698+
if (size > kMaxUint32 || size < 0) {
699+
throw new TypeError('size must be a uint32');
700+
}
701+
702+
if (size + offset > length || size > kBufferMaxLength) {
703+
throw new RangeError('buffer too small');
704+
}
705+
}
706+
637707
exports.randomBytes = exports.pseudoRandomBytes = randomBytes;
638708

639709
exports.rng = exports.prng = randomBytes;

src/node_crypto.cc

+97-14
Original file line numberDiff line numberDiff line change
@@ -5449,11 +5449,18 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
54495449
// Only instantiate within a valid HandleScope.
54505450
class RandomBytesRequest : public AsyncWrap {
54515451
public:
5452-
RandomBytesRequest(Environment* env, Local<Object> object, size_t size)
5452+
enum FreeMode { FREE_DATA, DONT_FREE_DATA };
5453+
5454+
RandomBytesRequest(Environment* env,
5455+
Local<Object> object,
5456+
size_t size,
5457+
char* data,
5458+
FreeMode free_mode)
54535459
: AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO),
54545460
error_(0),
54555461
size_(size),
5456-
data_(node::Malloc(size)) {
5462+
data_(data),
5463+
free_mode_(free_mode) {
54575464
Wrap(object, this);
54585465
}
54595466

@@ -5474,9 +5481,15 @@ class RandomBytesRequest : public AsyncWrap {
54745481
return data_;
54755482
}
54765483

5484+
inline void set_data(char* data) {
5485+
data_ = data;
5486+
}
5487+
54775488
inline void release() {
5478-
free(data_);
54795489
size_ = 0;
5490+
if (free_mode_ == FREE_DATA) {
5491+
free(data_);
5492+
}
54805493
}
54815494

54825495
inline void return_memory(char** d, size_t* len) {
@@ -5502,6 +5515,7 @@ class RandomBytesRequest : public AsyncWrap {
55025515
unsigned long error_; // NOLINT(runtime/int)
55035516
size_t size_;
55045517
char* data_;
5518+
const FreeMode free_mode_;
55055519
};
55065520

55075521

@@ -5540,7 +5554,18 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
55405554
size_t size;
55415555
req->return_memory(&data, &size);
55425556
argv[0] = Null(req->env()->isolate());
5543-
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
5557+
Local<Value> buffer =
5558+
req->object()->Get(req->env()->context(),
5559+
req->env()->buffer_string()).ToLocalChecked();
5560+
5561+
if (buffer->IsUint8Array()) {
5562+
CHECK_LE(req->size(), Buffer::Length(buffer));
5563+
char* buf = Buffer::Data(buffer);
5564+
memcpy(buf, data, req->size());
5565+
argv[1] = buffer;
5566+
} else {
5567+
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
5568+
}
55445569
}
55455570
}
55465571

@@ -5559,11 +5584,22 @@ void RandomBytesAfter(uv_work_t* work_req, int status) {
55595584
}
55605585

55615586

5587+
void RandomBytesProcessSync(Environment* env,
5588+
RandomBytesRequest* req,
5589+
Local<Value> argv[2]) {
5590+
env->PrintSyncTrace();
5591+
RandomBytesWork(req->work_req());
5592+
RandomBytesCheck(req, argv);
5593+
delete req;
5594+
5595+
if (!argv[0]->IsNull())
5596+
env->isolate()->ThrowException(argv[0]);
5597+
}
5598+
5599+
55625600
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
55635601
Environment* env = Environment::GetCurrent(args);
55645602

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

55755611
Local<Object> obj = env->NewInternalFieldObject();
5576-
RandomBytesRequest* req = new RandomBytesRequest(env, obj, size);
5612+
char* data = node::Malloc(size);
5613+
RandomBytesRequest* req =
5614+
new RandomBytesRequest(env,
5615+
obj,
5616+
size,
5617+
data,
5618+
RandomBytesRequest::FREE_DATA);
55775619

55785620
if (args[1]->IsFunction()) {
55795621
obj->Set(env->ondone_string(), args[1]);
@@ -5586,15 +5628,55 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
55865628
RandomBytesAfter);
55875629
args.GetReturnValue().Set(obj);
55885630
} else {
5589-
env->PrintSyncTrace();
55905631
Local<Value> argv[2];
5591-
RandomBytesWork(req->work_req());
5592-
RandomBytesCheck(req, argv);
5593-
delete req;
5632+
RandomBytesProcessSync(env, req, argv);
5633+
if (argv[0]->IsNull())
5634+
args.GetReturnValue().Set(argv[1]);
5635+
}
5636+
}
55945637

5595-
if (!argv[0]->IsNull())
5596-
env->isolate()->ThrowException(argv[0]);
5597-
else
5638+
5639+
void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
5640+
Environment* env = Environment::GetCurrent(args);
5641+
5642+
CHECK(args[0]->IsUint8Array());
5643+
CHECK(args[1]->IsUint32());
5644+
CHECK(args[2]->IsUint32());
5645+
5646+
int64_t offset = args[1]->IntegerValue();
5647+
int64_t size = args[2]->IntegerValue();
5648+
5649+
Local<Object> obj = env->NewInternalFieldObject();
5650+
obj->Set(env->context(), env->buffer_string(), args[0]).FromJust();
5651+
char* data = Buffer::Data(args[0]);
5652+
data += offset;
5653+
5654+
RandomBytesRequest* req =
5655+
new RandomBytesRequest(env,
5656+
obj,
5657+
size,
5658+
data,
5659+
RandomBytesRequest::DONT_FREE_DATA);
5660+
if (args[3]->IsFunction()) {
5661+
obj->Set(env->context(),
5662+
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"),
5663+
args[3]).FromJust();
5664+
5665+
if (env->in_domain()) {
5666+
obj->Set(env->context(),
5667+
env->domain_string(),
5668+
env->domain_array()->Get(0)).FromJust();
5669+
}
5670+
5671+
uv_queue_work(env->event_loop(),
5672+
req->work_req(),
5673+
RandomBytesWork,
5674+
RandomBytesAfter);
5675+
args.GetReturnValue().Set(obj);
5676+
} else {
5677+
Local<Value> argv[2];
5678+
RandomBytesProcessSync(env, req, argv);
5679+
if (argv[0]->IsNull())
55985680
args.GetReturnValue().Set(argv[1]);
55995681
}
56005682
}
@@ -6019,6 +6101,7 @@ void InitCrypto(Local<Object> target,
60196101
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
60206102
env->SetMethod(target, "PBKDF2", PBKDF2);
60216103
env->SetMethod(target, "randomBytes", RandomBytes);
6104+
env->SetMethod(target, "randomFill", RandomBytesBuffer);
60226105
env->SetMethod(target, "timingSafeEqual", TimingSafeEqual);
60236106
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
60246107
env->SetMethod(target, "getCiphers", GetCiphers);

0 commit comments

Comments
 (0)