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‎

Lines changed: 62 additions & 0 deletions
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+
constbuf=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+
constbuf=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‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ const setFipsCrypto = binding.setFipsCrypto;
1919
consttimingSafeEqual=binding.timingSafeEqual;
2020

2121
constBuffer=require('buffer').Buffer;
22+
constkBufferMaxLength=require('buffer').kMaxLength;
2223
conststream=require('stream');
2324
constutil=require('util');
25+
const{ isUint8Array }=process.binding('util');
2426
constLazyTransform=require('internal/streams/lazy_transform');
2527

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

639+
constkMaxUint32=Math.pow(2,32)-1;
640+
641+
functionrandomFillSync(buf,offset=0,size){
642+
if(!isUint8Array(buf)){
643+
thrownewTypeError('"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+
returnbinding.randomFill(buf,offset,size);
653+
}
654+
exports.randomFillSync=randomFillSync;
655+
656+
functionrandomFill(buf,offset,size,cb){
657+
if(!isUint8Array(buf)){
658+
thrownewTypeError('"buf" argument must be a Buffer or Uint8Array');
659+
}
660+
661+
if(typeofoffset==='function'){
662+
cb=offset;
663+
offset=0;
664+
size=buf.length;
665+
}elseif(typeofsize==='function'){
666+
cb=size;
667+
size=buf.length-offset;
668+
}elseif(typeofcb!=='function'){
669+
thrownewTypeError('"cb" argument must be a function');
670+
}
671+
672+
assertOffset(offset,buf.length);
673+
assertSize(size,offset,buf.length);
674+
675+
returnbinding.randomFill(buf,offset,size,cb);
676+
}
677+
exports.randomFill=randomFill;
678+
679+
functionassertOffset(offset,length){
680+
if(typeofoffset!=='number'||offset!==offset){
681+
thrownewTypeError('offset must be a number');
682+
}
683+
684+
if(offset>kMaxUint32||offset<0){
685+
thrownewTypeError('offset must be a uint32');
686+
}
687+
688+
if(offset>kBufferMaxLength||offset>length){
689+
thrownewRangeError('offset out of range');
690+
}
691+
}
692+
693+
functionassertSize(size,offset,length){
694+
if(typeofsize!=='number'||size!==size){
695+
thrownewTypeError('size must be a number');
696+
}
697+
698+
if(size>kMaxUint32||size<0){
699+
thrownewTypeError('size must be a uint32');
700+
}
701+
702+
if(size+offset>length||size>kBufferMaxLength){
703+
thrownewRangeError('buffer too small');
704+
}
705+
}
706+
637707
exports.randomBytes=exports.pseudoRandomBytes=randomBytes;
638708

639709
exports.rng=exports.prng=randomBytes;

‎src/node_crypto.cc‎

Lines changed: 97 additions & 14 deletions
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
classRandomBytesRequest : publicAsyncWrap{
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+
inlinevoidset_data(char* data){
5485+
data_ = data;
5486+
}
5487+
54775488
inlinevoidrelease(){
5478-
free(data_);
54795489
size_ = 0;
5490+
if (free_mode_ == FREE_DATA){
5491+
free(data_);
5492+
}
54805493
}
54815494

54825495
inlinevoidreturn_memory(char** d, size_t* len){
@@ -5502,6 +5515,7 @@ class RandomBytesRequest : public AsyncWrap{
55025515
unsignedlong 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+
voidRandomBytesProcessSync(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
voidRandomBytes(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 = newRandomBytesRequest(env, obj, size);
5612+
char* data = node::Malloc(size);
5613+
RandomBytesRequest* req =
5614+
newRandomBytesRequest(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+
voidRandomBytesBuffer(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+
newRandomBytesRequest(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)