Skip to content

Commit 078bb0f

Browse files
committed
crypto: refactor randomBytes()
Use the scrypt() infrastructure to reimplement randomBytes() and randomFill() in a simpler manner. PR-URL: nodejs#20816 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent c188cc5 commit 078bb0f

File tree

4 files changed

+65
-211
lines changed

4 files changed

+65
-211
lines changed

lib/internal/crypto/random.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
'use strict';
22

3+
const { AsyncWrap, Providers } = process.binding('async_wrap');
4+
const { Buffer } = require('buffer');
5+
const { randomBytes: _randomBytes } = process.binding('crypto');
36
const {
47
ERR_INVALID_ARG_TYPE,
58
ERR_INVALID_CALLBACK,
69
ERR_OUT_OF_RANGE
710
} = require('internal/errors').codes;
811
const { isArrayBufferView } = require('internal/util/types');
9-
const {
10-
randomBytes: _randomBytes,
11-
randomFill: _randomFill
12-
} = process.binding('crypto');
1312

1413
const { kMaxLength } = require('buffer');
1514
const kMaxUint32 = Math.pow(2, 32) - 1;
@@ -27,7 +26,7 @@ function assertOffset(offset, elementSize, length) {
2726
throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset);
2827
}
2928

30-
return offset;
29+
return offset >>> 0; // Convert to uint32.
3130
}
3231

3332
function assertSize(size, elementSize, offset, length) {
@@ -46,14 +45,25 @@ function assertSize(size, elementSize, offset, length) {
4645
throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset);
4746
}
4847

49-
return size;
48+
return size >>> 0; // Convert to uint32.
5049
}
5150

5251
function randomBytes(size, cb) {
53-
assertSize(size, 1, 0, Infinity);
52+
size = assertSize(size, 1, 0, Infinity);
5453
if (cb !== undefined && typeof cb !== 'function')
5554
throw new ERR_INVALID_CALLBACK();
56-
return _randomBytes(size, cb);
55+
56+
const buf = Buffer.alloc(size);
57+
58+
if (!cb) return handleError(buf, 0, size);
59+
60+
const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST);
61+
wrap.ondone = (ex) => { // Retains buf while request is in flight.
62+
if (ex) return cb.call(wrap, ex);
63+
cb.call(wrap, null, buf);
64+
};
65+
66+
_randomBytes(buf, 0, size, wrap);
5767
}
5868

5969
function randomFillSync(buf, offset = 0, size) {
@@ -71,7 +81,7 @@ function randomFillSync(buf, offset = 0, size) {
7181
size = assertSize(size, elementSize, offset, buf.byteLength);
7282
}
7383

74-
return _randomFill(buf, offset, size);
84+
return handleError(buf, offset, size);
7585
}
7686

7787
function randomFill(buf, offset, size, cb) {
@@ -100,7 +110,19 @@ function randomFill(buf, offset, size, cb) {
100110
size = assertSize(size, elementSize, offset, buf.byteLength);
101111
}
102112

103-
return _randomFill(buf, offset, size, cb);
113+
const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST);
114+
wrap.ondone = (ex) => { // Retains buf while request is in flight.
115+
if (ex) return cb.call(wrap, ex);
116+
cb.call(wrap, null, buf);
117+
};
118+
119+
_randomBytes(buf, offset, size, wrap);
120+
}
121+
122+
function handleError(buf, offset, size) {
123+
const ex = _randomBytes(buf, offset, size);
124+
if (ex) throw ex;
125+
return buf;
104126
}
105127

106128
module.exports = {

src/env.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,6 @@ struct PackageConfig {
344344
V(promise_reject_unhandled_function, v8::Function) \
345345
V(promise_wrap_template, v8::ObjectTemplate) \
346346
V(push_values_to_array_function, v8::Function) \
347-
V(randombytes_constructor_template, v8::ObjectTemplate) \
348347
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
349348
V(script_context_constructor_template, v8::FunctionTemplate) \
350349
V(script_data_constructor_function, v8::Function) \

src/node_crypto.cc

Lines changed: 32 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ using v8::NewStringType;
8282
using v8::Nothing;
8383
using v8::Null;
8484
using v8::Object;
85-
using v8::ObjectTemplate;
8685
using v8::PropertyAttribute;
8786
using v8::ReadOnly;
8887
using v8::Signature;
@@ -4586,208 +4585,50 @@ inline void CopyBuffer(Local<Value> buf, std::vector<char>* vec) {
45864585
}
45874586

45884587

4589-
// Only instantiate within a valid HandleScope.
4590-
class RandomBytesRequest : public AsyncWrap, public ThreadPoolWork {
4591-
public:
4592-
enum FreeMode { FREE_DATA, DONT_FREE_DATA };
4593-
4594-
RandomBytesRequest(Environment* env,
4595-
Local<Object> object,
4596-
size_t size,
4597-
char* data,
4598-
FreeMode free_mode)
4599-
: AsyncWrap(env, object, AsyncWrap::PROVIDER_RANDOMBYTESREQUEST),
4600-
ThreadPoolWork(env),
4601-
error_(0),
4602-
size_(size),
4603-
data_(data),
4604-
free_mode_(free_mode) {
4605-
}
4606-
4607-
inline size_t size() const {
4608-
return size_;
4609-
}
4610-
4611-
inline char* data() const {
4612-
return data_;
4613-
}
4614-
4615-
inline void set_data(char* data) {
4616-
data_ = data;
4617-
}
4588+
struct RandomBytesJob : public CryptoJob {
4589+
unsigned char* data;
4590+
size_t size;
4591+
CryptoErrorVector errors;
4592+
Maybe<int> rc;
46184593

4619-
inline void release() {
4620-
size_ = 0;
4621-
if (free_mode_ == FREE_DATA) {
4622-
free(data_);
4623-
data_ = nullptr;
4624-
}
4625-
}
4594+
inline explicit RandomBytesJob(Environment* env)
4595+
: CryptoJob(env), rc(Nothing<int>()) {}
46264596

4627-
inline void return_memory(char** d, size_t* len) {
4628-
*d = data_;
4629-
data_ = nullptr;
4630-
*len = size_;
4631-
size_ = 0;
4597+
inline void DoThreadPoolWork() override {
4598+
CheckEntropy(); // Ensure that OpenSSL's PRNG is properly seeded.
4599+
rc = Just(RAND_bytes(data, size));
4600+
if (0 == rc.FromJust()) errors.Capture();
46324601
}
46334602

4634-
inline unsigned long error() const { // NOLINT(runtime/int)
4635-
return error_;
4603+
inline void AfterThreadPoolWork() override {
4604+
Local<Value> arg = ToResult();
4605+
async_wrap->MakeCallback(env->ondone_string(), 1, &arg);
46364606
}
46374607

4638-
inline void set_error(unsigned long err) { // NOLINT(runtime/int)
4639-
error_ = err;
4608+
inline Local<Value> ToResult() const {
4609+
if (errors.empty()) return Undefined(env->isolate());
4610+
return errors.ToException(env);
46404611
}
4641-
4642-
size_t self_size() const override { return sizeof(*this); }
4643-
4644-
void DoThreadPoolWork() override;
4645-
void AfterThreadPoolWork(int status) override;
4646-
4647-
private:
4648-
unsigned long error_; // NOLINT(runtime/int)
4649-
size_t size_;
4650-
char* data_;
4651-
const FreeMode free_mode_;
46524612
};
46534613

46544614

4655-
void RandomBytesRequest::DoThreadPoolWork() {
4656-
// Ensure that OpenSSL's PRNG is properly seeded.
4657-
CheckEntropy();
4658-
4659-
const int r = RAND_bytes(reinterpret_cast<unsigned char*>(data_), size_);
4660-
4661-
// RAND_bytes() returns 0 on error.
4662-
if (r == 0) {
4663-
set_error(ERR_get_error()); // NOLINT(runtime/int)
4664-
} else if (r == -1) {
4665-
set_error(static_cast<unsigned long>(-1)); // NOLINT(runtime/int)
4666-
}
4667-
}
4668-
4669-
4670-
// don't call this function without a valid HandleScope
4671-
void RandomBytesCheck(RandomBytesRequest* req, Local<Value> (*argv)[2]) {
4672-
if (req->error()) {
4673-
char errmsg[256] = "Operation not supported";
4674-
4675-
if (req->error() != static_cast<unsigned long>(-1)) // NOLINT(runtime/int)
4676-
ERR_error_string_n(req->error(), errmsg, sizeof errmsg);
4677-
4678-
(*argv)[0] = Exception::Error(OneByteString(req->env()->isolate(), errmsg));
4679-
(*argv)[1] = Null(req->env()->isolate());
4680-
req->release();
4681-
} else {
4682-
char* data = nullptr;
4683-
size_t size;
4684-
req->return_memory(&data, &size);
4685-
(*argv)[0] = Null(req->env()->isolate());
4686-
Local<Value> buffer =
4687-
req->object()->Get(req->env()->context(),
4688-
req->env()->buffer_string()).ToLocalChecked();
4689-
4690-
if (buffer->IsArrayBufferView()) {
4691-
CHECK_LE(req->size(), Buffer::Length(buffer));
4692-
char* buf = Buffer::Data(buffer);
4693-
memcpy(buf, data, req->size());
4694-
(*argv)[1] = buffer;
4695-
} else {
4696-
(*argv)[1] = Buffer::New(req->env(), data, size)
4697-
.ToLocalChecked();
4698-
}
4699-
}
4700-
}
4701-
4702-
4703-
void RandomBytesRequest::AfterThreadPoolWork(int status) {
4704-
std::unique_ptr<RandomBytesRequest> req(this);
4705-
if (status == UV_ECANCELED)
4706-
return;
4707-
CHECK_EQ(status, 0);
4708-
HandleScope handle_scope(env()->isolate());
4709-
Context::Scope context_scope(env()->context());
4710-
Local<Value> argv[2];
4711-
RandomBytesCheck(this, &argv);
4712-
MakeCallback(env()->ondone_string(), arraysize(argv), argv);
4713-
}
4714-
4715-
4716-
void RandomBytesProcessSync(Environment* env,
4717-
std::unique_ptr<RandomBytesRequest> req,
4718-
Local<Value> (*argv)[2]) {
4719-
env->PrintSyncTrace();
4720-
req->DoThreadPoolWork();
4721-
RandomBytesCheck(req.get(), argv);
4722-
4723-
if (!(*argv)[0]->IsNull())
4724-
env->isolate()->ThrowException((*argv)[0]);
4725-
}
4726-
4727-
47284615
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
4616+
CHECK(args[0]->IsArrayBufferView()); // buffer; wrap object retains ref.
4617+
CHECK(args[1]->IsUint32()); // offset
4618+
CHECK(args[2]->IsUint32()); // size
4619+
CHECK(args[3]->IsObject() || args[3]->IsUndefined()); // wrap object
4620+
const uint32_t offset = args[1].As<Uint32>()->Value();
4621+
const uint32_t size = args[2].As<Uint32>()->Value();
4622+
CHECK_GE(offset + size, offset); // Overflow check.
4623+
CHECK_LE(offset + size, Buffer::Length(args[0])); // Bounds check.
47294624
Environment* env = Environment::GetCurrent(args);
4730-
4731-
const int64_t size = args[0]->IntegerValue();
4732-
CHECK(size <= Buffer::kMaxLength);
4733-
4734-
Local<Object> obj = env->randombytes_constructor_template()->
4735-
NewInstance(env->context()).ToLocalChecked();
4736-
char* data = node::Malloc(size);
4737-
std::unique_ptr<RandomBytesRequest> req(
4738-
new RandomBytesRequest(env,
4739-
obj,
4740-
size,
4741-
data,
4742-
RandomBytesRequest::FREE_DATA));
4743-
4744-
if (args[1]->IsFunction()) {
4745-
obj->Set(env->context(), env->ondone_string(), args[1]).FromJust();
4746-
4747-
req.release()->ScheduleWork();
4748-
args.GetReturnValue().Set(obj);
4749-
} else {
4750-
Local<Value> argv[2];
4751-
RandomBytesProcessSync(env, std::move(req), &argv);
4752-
if (argv[0]->IsNull())
4753-
args.GetReturnValue().Set(argv[1]);
4754-
}
4755-
}
4756-
4757-
4758-
void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) {
4759-
Environment* env = Environment::GetCurrent(args);
4760-
4761-
CHECK(args[0]->IsArrayBufferView());
4762-
CHECK(args[1]->IsUint32());
4763-
CHECK(args[2]->IsUint32());
4764-
4765-
int64_t offset = args[1]->IntegerValue();
4766-
int64_t size = args[2]->IntegerValue();
4767-
4768-
Local<Object> obj = env->randombytes_constructor_template()->
4769-
NewInstance(env->context()).ToLocalChecked();
4770-
obj->Set(env->context(), env->buffer_string(), args[0]).FromJust();
4771-
char* data = Buffer::Data(args[0]);
4772-
data += offset;
4773-
4774-
std::unique_ptr<RandomBytesRequest> req(
4775-
new RandomBytesRequest(env,
4776-
obj,
4777-
size,
4778-
data,
4779-
RandomBytesRequest::DONT_FREE_DATA));
4780-
if (args[3]->IsFunction()) {
4781-
obj->Set(env->context(), env->ondone_string(), args[3]).FromJust();
4782-
4783-
req.release()->ScheduleWork();
4784-
args.GetReturnValue().Set(obj);
4785-
} else {
4786-
Local<Value> argv[2];
4787-
RandomBytesProcessSync(env, std::move(req), &argv);
4788-
if (argv[0]->IsNull())
4789-
args.GetReturnValue().Set(argv[1]);
4790-
}
4625+
std::unique_ptr<RandomBytesJob> job(new RandomBytesJob(env));
4626+
job->data = reinterpret_cast<unsigned char*>(Buffer::Data(args[0])) + offset;
4627+
job->size = size;
4628+
if (args[3]->IsObject()) return RandomBytesJob::Run(std::move(job), args[3]);
4629+
env->PrintSyncTrace();
4630+
job->DoThreadPoolWork();
4631+
args.GetReturnValue().Set(job->ToResult());
47914632
}
47924633

47934634

@@ -5352,7 +5193,6 @@ void Initialize(Local<Object> target,
53525193

53535194
env->SetMethod(target, "pbkdf2", PBKDF2);
53545195
env->SetMethod(target, "randomBytes", RandomBytes);
5355-
env->SetMethod(target, "randomFill", RandomBytesBuffer);
53565196
env->SetMethod(target, "timingSafeEqual", TimingSafeEqual);
53575197
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
53585198
env->SetMethod(target, "getCiphers", GetCiphers);
@@ -5377,13 +5217,6 @@ void Initialize(Local<Object> target,
53775217
#ifndef OPENSSL_NO_SCRYPT
53785218
env->SetMethod(target, "scrypt", Scrypt);
53795219
#endif // OPENSSL_NO_SCRYPT
5380-
5381-
Local<FunctionTemplate> rb = FunctionTemplate::New(env->isolate());
5382-
rb->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "RandomBytes"));
5383-
AsyncWrap::AddWrapMethods(env, rb);
5384-
Local<ObjectTemplate> rbt = rb->InstanceTemplate();
5385-
rbt->SetInternalFieldCount(1);
5386-
env->set_randombytes_constructor_template(rbt);
53875220
}
53885221

53895222
} // namespace crypto

test/sequential/test-async-wrap-getasyncid.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
120120
crypto.pbkdf2('password', 'salt', 1, 20, 'sha256', mc);
121121

122122
crypto.randomBytes(1, common.mustCall(function rb() {
123-
testInitialized(this, 'RandomBytes');
123+
testInitialized(this, 'AsyncWrap');
124124
}));
125125

126126
if (typeof process.binding('crypto').scrypt === 'function') {

0 commit comments

Comments
 (0)