Skip to content

Commit a11f2ea

Browse files
committed
src,crypto: add NativeCryptoKey
Introduces a C++ `NativeCryptoKey` class that holds the real CryptoKey slots (`handle_data_`, `algorithm_`, `usages_`, `extractable_`) and provides structured-clone and worker-transfer support through a dedicated `CryptoKeyTransferData`. New bindings `createCryptoKeyClass`, `getCryptoKeyHandle`, and `getCryptoKeySlots` expose the class and accessors to JS; a brand-tag class-ID pointer rejects spoofed receivers on the accessors. Signed-off-by: Filip Skokan <[email protected]>
1 parent 8d518c4 commit a11f2ea

4 files changed

Lines changed: 399 additions & 0 deletions

File tree

src/crypto/crypto_keys.cc

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,313 @@ std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
17881788
return std::make_unique<KeyObjectTransferData>(handle_data_);
17891789
}
17901790

1791+
void NativeCryptoKey::Initialize(Environment* env, Local<Object> target) {
1792+
SetMethod(env->context(),
1793+
target,
1794+
"createCryptoKeyClass",
1795+
NativeCryptoKey::CreateCryptoKeyClass);
1796+
SetMethod(env->context(),
1797+
target,
1798+
"getCryptoKeyHandle",
1799+
NativeCryptoKey::GetKeyHandle);
1800+
SetMethod(
1801+
env->context(), target, "getCryptoKeySlots", NativeCryptoKey::GetSlots);
1802+
}
1803+
1804+
void NativeCryptoKey::RegisterExternalReferences(
1805+
ExternalReferenceRegistry* registry) {
1806+
registry->Register(NativeCryptoKey::CreateCryptoKeyClass);
1807+
registry->Register(NativeCryptoKey::GetKeyHandle);
1808+
registry->Register(NativeCryptoKey::GetSlots);
1809+
registry->Register(NativeCryptoKey::New);
1810+
}
1811+
1812+
namespace {
1813+
// Brand check: every NativeCryptoKey stores this pointer in its
1814+
// kClassTagField slot. Nothing else in the binary can produce the
1815+
// same pointer, so HasInstance() can use it to recognize a real
1816+
// NativeCryptoKey.
1817+
constexpr int kNativeCryptoKeyClassTag = 0;
1818+
const void* class_tag() {
1819+
return &kNativeCryptoKeyClassTag;
1820+
}
1821+
} // namespace
1822+
1823+
bool NativeCryptoKey::HasInstance(Local<Value> value) {
1824+
if (!value->IsObject()) return false;
1825+
Local<Object> obj = value.As<Object>();
1826+
if (obj->InternalFieldCount() < NativeCryptoKey::kInternalFieldCount) {
1827+
return false;
1828+
}
1829+
return obj->GetAlignedPointerFromInternalField(
1830+
NativeCryptoKey::kClassTagField, EmbedderDataTag::kDefault) ==
1831+
class_tag();
1832+
}
1833+
1834+
void NativeCryptoKey::New(const FunctionCallbackInfo<Value>& args) {
1835+
Environment* env = Environment::GetCurrent(args);
1836+
CHECK_EQ(args.Length(), 4);
1837+
// args[0] is a KeyObjectHandle; we keep its KeyObjectData directly.
1838+
// args[1] is the algorithm dictionary object.
1839+
// args[2] is the usages array of strings.
1840+
// args[3] is the extractable boolean.
1841+
//
1842+
// args[1] is undefined only when called from
1843+
// CryptoKeyTransferData::Deserialize for a partially-initialized
1844+
// CryptoKey: algorithm/usages/extractable get filled in afterwards
1845+
// by FinalizeTransferRead before any JS can see the object.
1846+
//
1847+
// This constructor is not exposed to user JS - the public CryptoKey
1848+
// class throws from its constructor and InternalCryptoKey is kept
1849+
// in a module-closure.
1850+
CHECK(KeyObjectHandle::HasInstance(env, args[0]));
1851+
KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
1852+
CHECK_NOT_NULL(handle);
1853+
1854+
auto* native = new NativeCryptoKey(env, args.This(), handle->Data());
1855+
1856+
// Brand-check tag for HasInstance().
1857+
args.This()->SetAlignedPointerInInternalField(kClassTagField,
1858+
const_cast<void*>(class_tag()),
1859+
EmbedderDataTag::kDefault);
1860+
1861+
if (!args[1]->IsUndefined()) {
1862+
CHECK(args[1]->IsObject());
1863+
CHECK(args[2]->IsArray());
1864+
CHECK(args[3]->IsBoolean());
1865+
native->algorithm_.Reset(env->isolate(), args[1].As<Object>());
1866+
native->usages_.Reset(env->isolate(), args[2].As<Array>());
1867+
native->extractable_ = args[3]->IsTrue();
1868+
}
1869+
}
1870+
1871+
void NativeCryptoKey::CreateCryptoKeyClass(
1872+
const FunctionCallbackInfo<Value>& args) {
1873+
Environment* env = Environment::GetCurrent(args);
1874+
Isolate* isolate = env->isolate();
1875+
1876+
CHECK_EQ(args.Length(), 1);
1877+
Local<Value> callback = args[0];
1878+
CHECK(callback->IsFunction());
1879+
1880+
Local<FunctionTemplate> t =
1881+
NewFunctionTemplate(isolate, NativeCryptoKey::New);
1882+
t->InstanceTemplate()->SetInternalFieldCount(
1883+
NativeCryptoKey::kInternalFieldCount);
1884+
1885+
Local<Value> ctor;
1886+
if (!t->GetFunction(env->context()).ToLocal(&ctor)) return;
1887+
1888+
Local<Value> recv = Undefined(env->isolate());
1889+
Local<Value> ret_v;
1890+
if (!callback.As<Function>()
1891+
->Call(env->context(), recv, 1, &ctor)
1892+
.ToLocal(&ret_v)) {
1893+
return;
1894+
}
1895+
Local<Array> ret = ret_v.As<Array>();
1896+
Local<Value> internal_ctor_v;
1897+
if (!ret->Get(env->context(), 1).ToLocal(&internal_ctor_v)) return;
1898+
env->set_crypto_internal_cryptokey_constructor(
1899+
internal_ctor_v.As<Function>());
1900+
args.GetReturnValue().Set(ret);
1901+
}
1902+
1903+
void NativeCryptoKey::GetKeyHandle(const FunctionCallbackInfo<Value>& args) {
1904+
Environment* env = Environment::GetCurrent(args);
1905+
CHECK_EQ(args.Length(), 1);
1906+
CHECK(HasInstance(args[0]));
1907+
NativeCryptoKey* native = Unwrap<NativeCryptoKey>(args[0].As<Object>());
1908+
Local<Object> handle;
1909+
if (!KeyObjectHandle::Create(env, native->handle_data_).ToLocal(&handle)) {
1910+
return;
1911+
}
1912+
args.GetReturnValue().Set(handle);
1913+
}
1914+
1915+
// Returns all of the key's internal slot values as a single Array:
1916+
// [type, extractable, algorithm, usages, handle]. JS-side helpers
1917+
// call this once per key to prime a per-instance cache, so subsequent
1918+
// reads don't need to cross into C++ at all.
1919+
void NativeCryptoKey::GetSlots(const FunctionCallbackInfo<Value>& args) {
1920+
Environment* env = Environment::GetCurrent(args);
1921+
CHECK_EQ(args.Length(), 1);
1922+
if (!HasInstance(args[0])) {
1923+
THROW_ERR_INVALID_THIS(env, "Value of \"this\" must be of type CryptoKey");
1924+
return;
1925+
}
1926+
Isolate* isolate = env->isolate();
1927+
NativeCryptoKey* native = Unwrap<NativeCryptoKey>(args[0].As<Object>());
1928+
1929+
const char* type_str;
1930+
switch (native->handle_data_.GetKeyType()) {
1931+
case kKeyTypeSecret:
1932+
type_str = "secret";
1933+
break;
1934+
case kKeyTypePublic:
1935+
type_str = "public";
1936+
break;
1937+
case kKeyTypePrivate:
1938+
type_str = "private";
1939+
break;
1940+
default:
1941+
UNREACHABLE();
1942+
}
1943+
1944+
Local<Object> handle;
1945+
if (!KeyObjectHandle::Create(env, native->handle_data_).ToLocal(&handle)) {
1946+
return;
1947+
}
1948+
1949+
CHECK(!native->algorithm_.IsEmpty());
1950+
CHECK(!native->usages_.IsEmpty());
1951+
Local<Value> slots[] = {
1952+
OneByteString(isolate, type_str),
1953+
v8::Boolean::New(isolate, native->extractable_),
1954+
PersistentToLocal::Strong(native->algorithm_),
1955+
PersistentToLocal::Strong(native->usages_),
1956+
handle,
1957+
};
1958+
args.GetReturnValue().Set(Array::New(isolate, slots, arraysize(slots)));
1959+
}
1960+
1961+
BaseObject::TransferMode NativeCryptoKey::GetTransferMode() const {
1962+
return BaseObject::TransferMode::kCloneable;
1963+
}
1964+
1965+
std::unique_ptr<worker::TransferData> NativeCryptoKey::CloneForMessaging()
1966+
const {
1967+
Isolate* isolate = env()->isolate();
1968+
CHECK(!algorithm_.IsEmpty());
1969+
CHECK(!usages_.IsEmpty());
1970+
v8::Global<Object> algorithm_copy(isolate,
1971+
PersistentToLocal::Strong(algorithm_));
1972+
v8::Global<Array> usages_copy(isolate, PersistentToLocal::Strong(usages_));
1973+
return std::make_unique<CryptoKeyTransferData>(handle_data_,
1974+
std::move(algorithm_copy),
1975+
std::move(usages_copy),
1976+
extractable_);
1977+
}
1978+
1979+
Maybe<void> NativeCryptoKey::FinalizeTransferRead(
1980+
Local<Context> context, v8::ValueDeserializer* deserializer) {
1981+
Local<Value> bundle_v;
1982+
if (!deserializer->ReadValue(context).ToLocal(&bundle_v)) {
1983+
return Nothing<void>();
1984+
}
1985+
CHECK(bundle_v->IsObject());
1986+
Local<Object> bundle = bundle_v.As<Object>();
1987+
Isolate* isolate = env()->isolate();
1988+
1989+
Local<Value> algorithm_v;
1990+
if (!bundle->Get(context, FIXED_ONE_BYTE_STRING(isolate, "algorithm"))
1991+
.ToLocal(&algorithm_v)) {
1992+
return Nothing<void>();
1993+
}
1994+
CHECK(algorithm_v->IsObject());
1995+
algorithm_.Reset(isolate, algorithm_v.As<Object>());
1996+
1997+
Local<Value> usages_v;
1998+
if (!bundle->Get(context, FIXED_ONE_BYTE_STRING(isolate, "usages"))
1999+
.ToLocal(&usages_v)) {
2000+
return Nothing<void>();
2001+
}
2002+
CHECK(usages_v->IsArray());
2003+
usages_.Reset(isolate, usages_v.As<Array>());
2004+
2005+
Local<Value> extractable_v;
2006+
if (!bundle->Get(context, FIXED_ONE_BYTE_STRING(isolate, "extractable"))
2007+
.ToLocal(&extractable_v)) {
2008+
return Nothing<void>();
2009+
}
2010+
CHECK(extractable_v->IsBoolean());
2011+
extractable_ = extractable_v->IsTrue();
2012+
2013+
return v8::JustVoid();
2014+
}
2015+
2016+
Maybe<bool> NativeCryptoKey::CryptoKeyTransferData::FinalizeTransferWrite(
2017+
Local<Context> context, v8::ValueSerializer* serializer) {
2018+
Isolate* isolate = Isolate::GetCurrent();
2019+
CHECK(!algorithm_.IsEmpty());
2020+
CHECK(!usages_.IsEmpty());
2021+
Local<Object> bundle = Object::New(isolate);
2022+
Local<Value> algorithm_v = PersistentToLocal::Strong(algorithm_);
2023+
Local<Value> usages_v = PersistentToLocal::Strong(usages_);
2024+
if (bundle
2025+
->Set(
2026+
context, FIXED_ONE_BYTE_STRING(isolate, "algorithm"), algorithm_v)
2027+
.IsNothing() ||
2028+
bundle->Set(context, FIXED_ONE_BYTE_STRING(isolate, "usages"), usages_v)
2029+
.IsNothing() ||
2030+
bundle
2031+
->Set(context,
2032+
FIXED_ONE_BYTE_STRING(isolate, "extractable"),
2033+
v8::Boolean::New(isolate, extractable_))
2034+
.IsNothing()) {
2035+
return Nothing<bool>();
2036+
}
2037+
auto ret = serializer->WriteValue(context, bundle);
2038+
algorithm_.Reset();
2039+
usages_.Reset();
2040+
return ret;
2041+
}
2042+
2043+
BaseObjectPtr<BaseObject> NativeCryptoKey::CryptoKeyTransferData::Deserialize(
2044+
Environment* env,
2045+
Local<Context> context,
2046+
std::unique_ptr<worker::TransferData> self) {
2047+
if (context != env->context()) {
2048+
THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
2049+
return {};
2050+
}
2051+
2052+
// Reconstruct the KeyObjectHandle for the transferred KeyObjectData.
2053+
Local<Object> handle;
2054+
if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle)) return {};
2055+
2056+
// Make sure internal/crypto/keys has been loaded so that the
2057+
// CryptoKey constructor is registered with the Environment.
2058+
Local<Value> arg =
2059+
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/crypto/keys");
2060+
if (env->builtin_module_require()
2061+
->Call(context, Null(env->isolate()), 1, &arg)
2062+
.IsEmpty()) {
2063+
return {};
2064+
}
2065+
2066+
// Construct a partially-initialized InternalCryptoKey; algorithm,
2067+
// usages and extractable are filled in via FinalizeTransferRead.
2068+
Local<Function> cryptokey_ctor = env->crypto_internal_cryptokey_constructor();
2069+
CHECK(!cryptokey_ctor.IsEmpty());
2070+
Local<Value> ctor_args[] = {
2071+
handle,
2072+
Undefined(env->isolate()),
2073+
Undefined(env->isolate()),
2074+
Undefined(env->isolate()),
2075+
};
2076+
Local<Value> cryptokey;
2077+
if (!cryptokey_ctor->NewInstance(context, 4, ctor_args).ToLocal(&cryptokey)) {
2078+
return {};
2079+
}
2080+
2081+
return BaseObjectPtr<BaseObject>(
2082+
Unwrap<NativeCryptoKey>(cryptokey.As<Object>()));
2083+
}
2084+
2085+
void NativeCryptoKey::MemoryInfo(MemoryTracker* tracker) const {
2086+
tracker->TrackField("handle_data", handle_data_);
2087+
tracker->TrackField("algorithm", algorithm_);
2088+
tracker->TrackField("usages", usages_);
2089+
}
2090+
2091+
void NativeCryptoKey::CryptoKeyTransferData::MemoryInfo(
2092+
MemoryTracker* tracker) const {
2093+
tracker->TrackField("data", data_);
2094+
tracker->TrackField("algorithm", algorithm_);
2095+
tracker->TrackField("usages", usages_);
2096+
}
2097+
17912098
namespace Keys {
17922099
void Initialize(Environment* env, Local<Object> target) {
17932100
target->Set(env->context(),

0 commit comments

Comments
 (0)