Skip to content

Commit b828560

Browse files
tniessenaddaleax
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 50b1cde commit b828560

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
@@ -1215,6 +1215,10 @@ This can be called many times with new data as it is streamed.
12151215
<!-- YAML
12161216
added: v11.6.0
12171217
changes:
1218+
- version: REPLACEME
1219+
pr-url: https://github.com/nodejs/node/pull/33360
1220+
description: Instances of this class can now be passed to worker threads
1221+
using `postMessage`.
12181222
- version: v11.13.0
12191223
pr-url: https://github.com/nodejs/node/pull/26438
12201224
description: This class is now exported.
@@ -1230,6 +1234,10 @@ keyword.
12301234
Most applications should consider using the new `KeyObject` API instead of
12311235
passing keys as strings or `Buffer`s due to improved security features.
12321236

1237+
`KeyObject` instances can be passed to other threads via [`postMessage()`][].
1238+
The receiver obtains a cloned `KeyObject`, and the `KeyObject` does not need to
1239+
be listed in the `transferList` argument.
1240+
12331241
### `keyObject.asymmetricKeyType`
12341242
<!-- YAML
12351243
added: v11.6.0
@@ -3560,6 +3568,7 @@ See the [list of SSL OP Flags][] for details.
35603568
[`hmac.digest()`]: #crypto_hmac_digest_encoding
35613569
[`hmac.update()`]: #crypto_hmac_update_data_inputencoding
35623570
[`keyObject.export()`]: #crypto_keyobject_export_options
3571+
[`postMessage()`]: worker_threads.html#worker_threads_port_postmessage_value_transferlist
35633572
[`sign.sign()`]: #crypto_sign_sign_privatekey_outputencoding
35643573
[`sign.update()`]: #crypto_sign_update_data_inputencoding
35653574
[`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');
@@ -846,6 +849,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
846849
[`EventEmitter`]: events.html
847850
[`EventTarget`]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
848851
[`FileHandle`]: fs.html#fs_class_filehandle
852+
[`KeyObject`]: crypto.html#crypto_class_keyobject
849853
[`MessagePort`]: #worker_threads_class_messageport
850854
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
851855
[`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
@@ -452,6 +452,9 @@ constexpr size_t kFsStatsBufferLength =
452452
V(buffer_prototype_object, v8::Object) \
453453
V(crypto_key_object_constructor, v8::Function) \
454454
V(crypto_key_object_handle_constructor, v8::Function) \
455+
V(crypto_key_object_private_constructor, v8::Function) \
456+
V(crypto_key_object_public_constructor, v8::Function) \
457+
V(crypto_key_object_secret_constructor, v8::Function) \
455458
V(domexception_function, v8::Function) \
456459
V(enhance_fatal_stack_after_inspector, v8::Function) \
457460
V(enhance_fatal_stack_before_inspector, v8::Function) \

src/node_crypto.cc

+83-34
Original file line numberDiff line numberDiff line change
@@ -3206,27 +3206,24 @@ EVP_PKEY* ManagedEVPPKey::get() const {
32063206
return pkey_.get();
32073207
}
32083208

3209-
KeyObjectData* KeyObjectData::CreateSecret(v8::Local<v8::ArrayBufferView> abv) {
3209+
std::shared_ptr<KeyObjectData> KeyObjectData::CreateSecret(
3210+
Local<ArrayBufferView> abv) {
32103211
size_t key_len = abv->ByteLength();
32113212
char* mem = MallocOpenSSL<char>(key_len);
32123213
abv->CopyContents(mem, key_len);
3213-
KeyObjectData* data = new KeyObjectData();
3214-
data->key_type_ = kKeyTypeSecret;
3215-
data->symmetric_key_ = std::unique_ptr<char, std::function<void(char*)>>(mem,
3214+
return std::shared_ptr<KeyObjectData>(new KeyObjectData(
3215+
std::unique_ptr<char, std::function<void(char*)>>(mem,
32163216
[key_len](char* p) {
32173217
OPENSSL_clear_free(p, key_len);
3218-
});
3219-
data->symmetric_key_len_ = key_len;
3220-
return data;
3218+
}),
3219+
key_len));
32213220
}
32223221

3223-
KeyObjectData* KeyObjectData::CreateAsymmetric(KeyType key_type,
3224-
const ManagedEVPPKey& pkey) {
3222+
std::shared_ptr<KeyObjectData> KeyObjectData::CreateAsymmetric(
3223+
KeyType key_type,
3224+
const ManagedEVPPKey& pkey) {
32253225
CHECK(pkey);
3226-
KeyObjectData* data = new KeyObjectData();
3227-
data->key_type_ = key_type;
3228-
data->asymmetric_key_ = pkey;
3229-
return data;
3226+
return std::shared_ptr<KeyObjectData>(new KeyObjectData(key_type, pkey));
32303227
}
32313228

32323229
KeyType KeyObjectData::GetKeyType() const {
@@ -3270,26 +3267,24 @@ Local<Function> KeyObjectHandle::Initialize(Environment* env,
32703267
return function;
32713268
}
32723269

3273-
MaybeLocal<Object> KeyObjectHandle::Create(Environment* env,
3274-
KeyType key_type,
3275-
const ManagedEVPPKey& pkey) {
3276-
CHECK_NE(key_type, kKeyTypeSecret);
3277-
Local<Value> type = Integer::New(env->isolate(), key_type);
3270+
MaybeLocal<Object> KeyObjectHandle::Create(
3271+
Environment* env,
3272+
std::shared_ptr<KeyObjectData> data) {
32783273
Local<Object> obj;
32793274
if (!env->crypto_key_object_handle_constructor()
3280-
->NewInstance(env->context(), 1, &type)
3275+
->NewInstance(env->context(), 0, nullptr)
32813276
.ToLocal(&obj)) {
32823277
return MaybeLocal<Object>();
32833278
}
32843279

32853280
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(obj);
32863281
CHECK_NOT_NULL(key);
3287-
key->data_.reset(KeyObjectData::CreateAsymmetric(key_type, pkey));
3282+
key->data_ = data;
32883283
return obj;
32893284
}
32903285

3291-
const KeyObjectData* KeyObjectHandle::Data() {
3292-
return data_.get();
3286+
const std::shared_ptr<KeyObjectData>& KeyObjectHandle::Data() {
3287+
return data_;
32933288
}
32943289

32953290
void KeyObjectHandle::New(const FunctionCallbackInfo<Value>& args) {
@@ -3319,8 +3314,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
33193314
case kKeyTypeSecret:
33203315
CHECK_EQ(args.Length(), 2);
33213316
CHECK(args[1]->IsArrayBufferView());
3322-
key->data_.reset(
3323-
KeyObjectData::CreateSecret(args[1].As<ArrayBufferView>()));
3317+
key->data_ = KeyObjectData::CreateSecret(args[1].As<ArrayBufferView>());
33243318
break;
33253319
case kKeyTypePublic:
33263320
CHECK_EQ(args.Length(), 4);
@@ -3329,7 +3323,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
33293323
pkey = GetPublicOrPrivateKeyFromJs(args, &offset);
33303324
if (!pkey)
33313325
return;
3332-
key->data_.reset(KeyObjectData::CreateAsymmetric(type, pkey));
3326+
key->data_ = KeyObjectData::CreateAsymmetric(type, pkey);
33333327
break;
33343328
case kKeyTypePrivate:
33353329
CHECK_EQ(args.Length(), 5);
@@ -3338,7 +3332,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
33383332
pkey = GetPrivateKeyFromJs(args, &offset, false);
33393333
if (!pkey)
33403334
return;
3341-
key->data_.reset(KeyObjectData::CreateAsymmetric(type, pkey));
3335+
key->data_ = KeyObjectData::CreateAsymmetric(type, pkey);
33423336
break;
33433337
default:
33443338
CHECK(false);
@@ -3434,7 +3428,50 @@ MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
34343428
}
34353429

34363430
void NativeKeyObject::New(const FunctionCallbackInfo<Value>& args) {
3437-
CHECK_EQ(args.Length(), 0);
3431+
Environment* env = Environment::GetCurrent(args);
3432+
CHECK_EQ(args.Length(), 1);
3433+
CHECK(args[0]->IsObject());
3434+
KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
3435+
new NativeKeyObject(env, args.This(), handle->Data());
3436+
}
3437+
3438+
BaseObjectPtr<BaseObject> NativeKeyObject::KeyObjectTransferData::Deserialize(
3439+
Environment* env,
3440+
Local<Context> context,
3441+
std::unique_ptr<worker::TransferData> self) {
3442+
if (context != env->context()) {
3443+
THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
3444+
return {};
3445+
}
3446+
3447+
Local<Value> handle = KeyObjectHandle::Create(env, data_).ToLocalChecked();
3448+
Local<Function> key_ctor;
3449+
switch (data_->GetKeyType()) {
3450+
case kKeyTypeSecret:
3451+
key_ctor = env->crypto_key_object_secret_constructor();
3452+
break;
3453+
case kKeyTypePublic:
3454+
key_ctor = env->crypto_key_object_public_constructor();
3455+
break;
3456+
case kKeyTypePrivate:
3457+
key_ctor = env->crypto_key_object_private_constructor();
3458+
break;
3459+
default:
3460+
CHECK(false);
3461+
}
3462+
3463+
Local<Value> key =
3464+
key_ctor->NewInstance(context, 1, &handle).ToLocalChecked();
3465+
return BaseObjectPtr<BaseObject>(Unwrap<KeyObjectHandle>(key.As<Object>()));
3466+
}
3467+
3468+
BaseObject::TransferMode NativeKeyObject::GetTransferMode() const {
3469+
return BaseObject::TransferMode::kCloneable;
3470+
}
3471+
3472+
std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
3473+
const {
3474+
return std::make_unique<KeyObjectTransferData>(handle_data_);
34383475
}
34393476

34403477
static void CreateNativeKeyObjectClass(
@@ -3448,13 +3485,23 @@ static void CreateNativeKeyObjectClass(
34483485
Local<FunctionTemplate> t = env->NewFunctionTemplate(NativeKeyObject::New);
34493486
t->InstanceTemplate()->SetInternalFieldCount(
34503487
KeyObjectHandle::kInternalFieldCount);
3488+
t->Inherit(BaseObject::GetConstructorTemplate(env));
34513489

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

34543492
Local<Value> recv = Undefined(env->isolate());
3455-
Local<Value> ret =
3456-
callback.As<Function>()->Call(env->context(), recv, 1, &ctor)
3457-
.ToLocalChecked();
3493+
Local<Value> ret_v;
3494+
if (!callback.As<Function>()->Call(
3495+
env->context(), recv, 1, &ctor).ToLocal(&ret_v)) {
3496+
return;
3497+
}
3498+
Local<Array> ret = ret_v.As<Array>();
3499+
if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return;
3500+
env->set_crypto_key_object_secret_constructor(ctor.As<Function>());
3501+
if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return;
3502+
env->set_crypto_key_object_public_constructor(ctor.As<Function>());
3503+
if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return;
3504+
env->set_crypto_key_object_private_constructor(ctor.As<Function>());
34583505
args.GetReturnValue().Set(ret);
34593506
}
34603507

@@ -6318,8 +6365,9 @@ class GenerateKeyPairJob : public CryptoJob {
63186365
if (public_key_encoding_.output_key_object_) {
63196366
// Note that this has the downside of containing sensitive data of the
63206367
// private key.
6321-
if (!KeyObjectHandle::Create(env(), kKeyTypePublic, pkey_)
6322-
.ToLocal(pubkey))
6368+
std::shared_ptr<KeyObjectData> data =
6369+
KeyObjectData::CreateAsymmetric(kKeyTypePublic, pkey_);
6370+
if (!KeyObjectHandle::Create(env(), data).ToLocal(pubkey))
63236371
return false;
63246372
} else {
63256373
if (!WritePublicKey(env(), pkey_.get(), public_key_encoding_)
@@ -6329,8 +6377,9 @@ class GenerateKeyPairJob : public CryptoJob {
63296377

63306378
// Now do the same for the private key.
63316379
if (private_key_encoding_.output_key_object_) {
6332-
if (!KeyObjectHandle::Create(env(), kKeyTypePrivate, pkey_)
6333-
.ToLocal(privkey))
6380+
std::shared_ptr<KeyObjectData> data =
6381+
KeyObjectData::CreateAsymmetric(kKeyTypePrivate, pkey_);
6382+
if (!KeyObjectHandle::Create(env(), data).ToLocal(privkey))
63346383
return false;
63356384
} else {
63366385
if (!WritePrivateKey(env(), pkey_.get(), private_key_encoding_)

0 commit comments

Comments
 (0)