Skip to content

Commit 70398db

Browse files
tniessencodebytere
authored andcommitted
crypto: allow KeyObjects in postMessage
This change allows sharing KeyObjects between threads via postMessage. The receiver acquires a new KeyObject and a new KeyObjectHandle, but refers to the same KeyObjectData: +-------------------+ | NativeKeyObject 1 | ------------------------------------------+ +-------------------+ | ^ | extends | | | +-------------------+ +-------------------+ | | KeyObject 1 (JS) | -> | KeyObjectHandle 1 | --------------+ | +-------------------+ +-------------------+ | | | | | | | | | | | | +-------------------+ | | | NativeKeyObject 2 | ------------------------------------+ | | +-------------------+ | | | ^ | | | extends | | | | | | | +-------------------+ +-------------------+ | | | | KeyObject 2 (JS) | -> | KeyObjectHandle 2 | --------+ | | | +-------------------+ +-------------------+ | | | | | | | | | | | | | | | | | | | | | | | | +-------------------+ | | | | | NativeKeyObject 3 | ------------------------------+ | | | | +-------------------+ | | | | | ^ | | | | | extends | | | | | | v v v v v +-------------------+ +-------------------+ +---------------+ | KeyObject 3 (JS) | -> | KeyObjectHandle 3 | -> | KeyObjectData | +-------------------+ +-------------------+ +---------------+ Co-authored-by: Anna Henningsen <[email protected]> PR-URL: #33360 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 3302025 commit 70398db

File tree

7 files changed

+252
-52
lines changed

7 files changed

+252
-52
lines changed

doc/api/crypto.md

+9
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,10 @@ This can be called many times with new data as it is streamed.
12141214
<!-- YAML
12151215
added: v11.6.0
12161216
changes:
1217+
- version: REPLACEME
1218+
pr-url: https://github.com/nodejs/node/pull/33360
1219+
description: Instances of this class can now be passed to worker threads
1220+
using `postMessage`.
12171221
- version: v11.13.0
12181222
pr-url: https://github.com/nodejs/node/pull/26438
12191223
description: This class is now exported.
@@ -1229,6 +1233,10 @@ keyword.
12291233
Most applications should consider using the new `KeyObject` API instead of
12301234
passing keys as strings or `Buffer`s due to improved security features.
12311235

1236+
`KeyObject` instances can be passed to other threads via [`postMessage()`][].
1237+
The receiver obtains a cloned `KeyObject`, and the `KeyObject` does not need to
1238+
be listed in the `transferList` argument.
1239+
12321240
### `keyObject.asymmetricKeyType`
12331241
<!-- YAML
12341242
added: v11.6.0
@@ -3510,6 +3518,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
35103518
[`hmac.digest()`]: #crypto_hmac_digest_encoding
35113519
[`hmac.update()`]: #crypto_hmac_update_data_inputencoding
35123520
[`keyObject.export()`]: #crypto_keyobject_export_options
3521+
[`postMessage()`]: worker_threads.html#worker_threads_port_postmessage_value_transferlist
35133522
[`sign.sign()`]: #crypto_sign_sign_privatekey_outputencoding
35143523
[`sign.update()`]: #crypto_sign_update_data_inputencoding
35153524
[`stream.Writable` options]: stream.html#stream_new_stream_writable_options

doc/api/worker_threads.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ are part of the channel.
328328
<!-- YAML
329329
added: v10.5.0
330330
changes:
331+
- version: REPLACEME
332+
pr-url: https://github.com/nodejs/node/pull/33360
333+
description: Added `KeyObject` to the list of cloneable types.
331334
- version: REPLACEME
332335
pr-url: https://github.com/nodejs/node/pull/33772
333336
description: Added `FileHandle` to the list of transferable types.
@@ -348,8 +351,8 @@ In particular, the significant differences to `JSON` are:
348351
* `value` may contain typed arrays, both using `ArrayBuffer`s
349352
and `SharedArrayBuffer`s.
350353
* `value` may contain [`WebAssembly.Module`][] instances.
351-
* `value` may not contain native (C++-backed) objects other than `MessagePort`s
352-
and [`FileHandle`][]s.
354+
* `value` may not contain native (C++-backed) objects other than `MessagePort`s,
355+
[`FileHandle`][]s, and [`KeyObject`][]s.
353356

354357
```js
355358
const { MessageChannel } = require('worker_threads');
@@ -843,6 +846,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
843846
[`EventEmitter`]: events.html
844847
[`EventTarget`]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
845848
[`FileHandle`]: fs.html#fs_class_filehandle
849+
[`KeyObject`]: crypto.html#crypto_class_keyobject
846850
[`MessagePort`]: #worker_threads_class_messageport
847851
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
848852
[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

lib/internal/crypto/keys.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
4343
[kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
4444
encodingNames[m[0]] = m[1];
4545

46+
function checkKeyTypeAndHandle(type, handle) {
47+
if (type !== 'secret' && type !== 'public' && type !== 'private')
48+
throw new ERR_INVALID_ARG_VALUE('type', type);
49+
if (typeof handle !== 'object' || !(handle instanceof KeyObjectHandle))
50+
throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle);
51+
}
52+
4653
// Creating the KeyObject class is a little complicated due to inheritance
4754
// and that fact that KeyObjects should be transferrable between threads,
4855
// which requires the KeyObject base class to be implemented in C++.
@@ -57,11 +64,7 @@ const [
5764
// Publicly visible KeyObject class.
5865
class KeyObject extends NativeKeyObject {
5966
constructor(type, handle) {
60-
super();
61-
if (type !== 'secret' && type !== 'public' && type !== 'private')
62-
throw new ERR_INVALID_ARG_VALUE('type', type);
63-
if (typeof handle !== 'object')
64-
throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle);
67+
super(checkKeyTypeAndHandle(type, handle) || handle);
6568

6669
this[kKeyType] = type;
6770

src/env.h

+3
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ constexpr size_t kFsStatsBufferLength =
442442
V(buffer_prototype_object, v8::Object) \
443443
V(crypto_key_object_constructor, v8::Function) \
444444
V(crypto_key_object_handle_constructor, v8::Function) \
445+
V(crypto_key_object_private_constructor, v8::Function) \
446+
V(crypto_key_object_public_constructor, v8::Function) \
447+
V(crypto_key_object_secret_constructor, v8::Function) \
445448
V(domexception_function, v8::Function) \
446449
V(enhance_fatal_stack_after_inspector, v8::Function) \
447450
V(enhance_fatal_stack_before_inspector, v8::Function) \

src/node_crypto.cc

+83-34
Original file line numberDiff line numberDiff line change
@@ -3244,27 +3244,24 @@ EVP_PKEY* ManagedEVPPKey::get() const {
32443244
return pkey_.get();
32453245
}
32463246

3247-
KeyObjectData* KeyObjectData::CreateSecret(v8::Local<v8::ArrayBufferView> abv) {
3247+
std::shared_ptr<KeyObjectData> KeyObjectData::CreateSecret(
3248+
Local<ArrayBufferView> abv) {
32483249
size_t key_len = abv->ByteLength();
32493250
char* mem = MallocOpenSSL<char>(key_len);
32503251
abv->CopyContents(mem, key_len);
3251-
KeyObjectData* data = new KeyObjectData();
3252-
data->key_type_ = kKeyTypeSecret;
3253-
data->symmetric_key_ = std::unique_ptr<char, std::function<void(char*)>>(mem,
3252+
return std::shared_ptr<KeyObjectData>(new KeyObjectData(
3253+
std::unique_ptr<char, std::function<void(char*)>>(mem,
32543254
[key_len](char* p) {
32553255
OPENSSL_clear_free(p, key_len);
3256-
});
3257-
data->symmetric_key_len_ = key_len;
3258-
return data;
3256+
}),
3257+
key_len));
32593258
}
32603259

3261-
KeyObjectData* KeyObjectData::CreateAsymmetric(KeyType key_type,
3262-
const ManagedEVPPKey& pkey) {
3260+
std::shared_ptr<KeyObjectData> KeyObjectData::CreateAsymmetric(
3261+
KeyType key_type,
3262+
const ManagedEVPPKey& pkey) {
32633263
CHECK(pkey);
3264-
KeyObjectData* data = new KeyObjectData();
3265-
data->key_type_ = key_type;
3266-
data->asymmetric_key_ = pkey;
3267-
return data;
3264+
return std::shared_ptr<KeyObjectData>(new KeyObjectData(key_type, pkey));
32683265
}
32693266

32703267
KeyType KeyObjectData::GetKeyType() const {
@@ -3308,26 +3305,24 @@ Local<Function> KeyObjectHandle::Initialize(Environment* env,
33083305
return function;
33093306
}
33103307

3311-
MaybeLocal<Object> KeyObjectHandle::Create(Environment* env,
3312-
KeyType key_type,
3313-
const ManagedEVPPKey& pkey) {
3314-
CHECK_NE(key_type, kKeyTypeSecret);
3315-
Local<Value> type = Integer::New(env->isolate(), key_type);
3308+
MaybeLocal<Object> KeyObjectHandle::Create(
3309+
Environment* env,
3310+
std::shared_ptr<KeyObjectData> data) {
33163311
Local<Object> obj;
33173312
if (!env->crypto_key_object_handle_constructor()
3318-
->NewInstance(env->context(), 1, &type)
3313+
->NewInstance(env->context(), 0, nullptr)
33193314
.ToLocal(&obj)) {
33203315
return MaybeLocal<Object>();
33213316
}
33223317

33233318
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(obj);
33243319
CHECK_NOT_NULL(key);
3325-
key->data_.reset(KeyObjectData::CreateAsymmetric(key_type, pkey));
3320+
key->data_ = data;
33263321
return obj;
33273322
}
33283323

3329-
const KeyObjectData* KeyObjectHandle::Data() {
3330-
return data_.get();
3324+
const std::shared_ptr<KeyObjectData>& KeyObjectHandle::Data() {
3325+
return data_;
33313326
}
33323327

33333328
void KeyObjectHandle::New(const FunctionCallbackInfo<Value>& args) {
@@ -3357,8 +3352,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
33573352
case kKeyTypeSecret:
33583353
CHECK_EQ(args.Length(), 2);
33593354
CHECK(args[1]->IsArrayBufferView());
3360-
key->data_.reset(
3361-
KeyObjectData::CreateSecret(args[1].As<ArrayBufferView>()));
3355+
key->data_ = KeyObjectData::CreateSecret(args[1].As<ArrayBufferView>());
33623356
break;
33633357
case kKeyTypePublic:
33643358
CHECK_EQ(args.Length(), 4);
@@ -3367,7 +3361,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
33673361
pkey = GetPublicOrPrivateKeyFromJs(args, &offset);
33683362
if (!pkey)
33693363
return;
3370-
key->data_.reset(KeyObjectData::CreateAsymmetric(type, pkey));
3364+
key->data_ = KeyObjectData::CreateAsymmetric(type, pkey);
33713365
break;
33723366
case kKeyTypePrivate:
33733367
CHECK_EQ(args.Length(), 5);
@@ -3376,7 +3370,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
33763370
pkey = GetPrivateKeyFromJs(args, &offset, false);
33773371
if (!pkey)
33783372
return;
3379-
key->data_.reset(KeyObjectData::CreateAsymmetric(type, pkey));
3373+
key->data_ = KeyObjectData::CreateAsymmetric(type, pkey);
33803374
break;
33813375
default:
33823376
CHECK(false);
@@ -3472,7 +3466,50 @@ MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
34723466
}
34733467

34743468
void NativeKeyObject::New(const FunctionCallbackInfo<Value>& args) {
3475-
CHECK_EQ(args.Length(), 0);
3469+
Environment* env = Environment::GetCurrent(args);
3470+
CHECK_EQ(args.Length(), 1);
3471+
CHECK(args[0]->IsObject());
3472+
KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
3473+
new NativeKeyObject(env, args.This(), handle->Data());
3474+
}
3475+
3476+
BaseObjectPtr<BaseObject> NativeKeyObject::KeyObjectTransferData::Deserialize(
3477+
Environment* env,
3478+
Local<Context> context,
3479+
std::unique_ptr<worker::TransferData> self) {
3480+
if (context != env->context()) {
3481+
THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
3482+
return {};
3483+
}
3484+
3485+
Local<Value> handle = KeyObjectHandle::Create(env, data_).ToLocalChecked();
3486+
Local<Function> key_ctor;
3487+
switch (data_->GetKeyType()) {
3488+
case kKeyTypeSecret:
3489+
key_ctor = env->crypto_key_object_secret_constructor();
3490+
break;
3491+
case kKeyTypePublic:
3492+
key_ctor = env->crypto_key_object_public_constructor();
3493+
break;
3494+
case kKeyTypePrivate:
3495+
key_ctor = env->crypto_key_object_private_constructor();
3496+
break;
3497+
default:
3498+
CHECK(false);
3499+
}
3500+
3501+
Local<Value> key =
3502+
key_ctor->NewInstance(context, 1, &handle).ToLocalChecked();
3503+
return BaseObjectPtr<BaseObject>(Unwrap<KeyObjectHandle>(key.As<Object>()));
3504+
}
3505+
3506+
BaseObject::TransferMode NativeKeyObject::GetTransferMode() const {
3507+
return BaseObject::TransferMode::kCloneable;
3508+
}
3509+
3510+
std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
3511+
const {
3512+
return std::make_unique<KeyObjectTransferData>(handle_data_);
34763513
}
34773514

34783515
static void CreateNativeKeyObjectClass(
@@ -3486,13 +3523,23 @@ static void CreateNativeKeyObjectClass(
34863523
Local<FunctionTemplate> t = env->NewFunctionTemplate(NativeKeyObject::New);
34873524
t->InstanceTemplate()->SetInternalFieldCount(
34883525
KeyObjectHandle::kInternalFieldCount);
3526+
t->Inherit(BaseObject::GetConstructorTemplate(env));
34893527

34903528
Local<Value> ctor = t->GetFunction(env->context()).ToLocalChecked();
34913529

34923530
Local<Value> recv = Undefined(env->isolate());
3493-
Local<Value> ret =
3494-
callback.As<Function>()->Call(env->context(), recv, 1, &ctor)
3495-
.ToLocalChecked();
3531+
Local<Value> ret_v;
3532+
if (!callback.As<Function>()->Call(
3533+
env->context(), recv, 1, &ctor).ToLocal(&ret_v)) {
3534+
return;
3535+
}
3536+
Local<Array> ret = ret_v.As<Array>();
3537+
if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return;
3538+
env->set_crypto_key_object_secret_constructor(ctor.As<Function>());
3539+
if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return;
3540+
env->set_crypto_key_object_public_constructor(ctor.As<Function>());
3541+
if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return;
3542+
env->set_crypto_key_object_private_constructor(ctor.As<Function>());
34963543
args.GetReturnValue().Set(ret);
34973544
}
34983545

@@ -6308,8 +6355,9 @@ class GenerateKeyPairJob : public CryptoJob {
63086355
if (public_key_encoding_.output_key_object_) {
63096356
// Note that this has the downside of containing sensitive data of the
63106357
// private key.
6311-
if (!KeyObjectHandle::Create(env(), kKeyTypePublic, pkey_)
6312-
.ToLocal(pubkey))
6358+
std::shared_ptr<KeyObjectData> data =
6359+
KeyObjectData::CreateAsymmetric(kKeyTypePublic, pkey_);
6360+
if (!KeyObjectHandle::Create(env(), data).ToLocal(pubkey))
63136361
return false;
63146362
} else {
63156363
if (!WritePublicKey(env(), pkey_.get(), public_key_encoding_)
@@ -6319,8 +6367,9 @@ class GenerateKeyPairJob : public CryptoJob {
63196367

63206368
// Now do the same for the private key.
63216369
if (private_key_encoding_.output_key_object_) {
6322-
if (!KeyObjectHandle::Create(env(), kKeyTypePrivate, pkey_)
6323-
.ToLocal(privkey))
6370+
std::shared_ptr<KeyObjectData> data =
6371+
KeyObjectData::CreateAsymmetric(kKeyTypePrivate, pkey_);
6372+
if (!KeyObjectHandle::Create(env(), data).ToLocal(privkey))
63246373
return false;
63256374
} else {
63266375
if (!WritePrivateKey(env(), pkey_.get(), private_key_encoding_)

0 commit comments

Comments
 (0)