From 5ae92ffd038e4a94052e2cb705e586339857bb7a Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 23 Aug 2026 18:03:51 +0200 Subject: [PATCH 1/2] crypto: avoid throwing KeyObject brand checks Use the native KeyObject FunctionTemplate brand check instead of probing slot access and catching ERR_INVALID_THIS. Keep a private-field fast path for ordinary same-realm KeyObjects. Signed-off-by: Filip Skokan --- lib/internal/crypto/keys.js | 18 ++++++------------ src/crypto/crypto_keys.cc | 9 +++++++++ src/crypto/crypto_keys.h | 1 + typings/internalBinding/crypto.d.ts | 1 + 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 327a6352d5c9..7452d956cef1 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -16,6 +16,7 @@ const { createNativeKeyObjectClass, // eslint-disable-next-line no-restricted-syntax -- intended here getKeyObjectSlots: nativeGetKeyObjectSlots, + isKeyObject: isNativeKeyObject, createCryptoKeyClass, // eslint-disable-next-line no-restricted-syntax -- intended here getCryptoKeySlots: nativeGetCryptoKeySlots, @@ -106,6 +107,7 @@ for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'], // slot tuple in a private field so no forgeable own Symbols are exposed on // public KeyObject instances. let getKeyObjectSlots; // Populated by the createNativeKeyObjectClass callback. +let isKeyObject; const kKeyObjectSlotType = 0; const kKeyObjectSlotHandle = 1; @@ -216,6 +218,10 @@ const { } static { + isKeyObject = (key) => { + if (key == null || typeof key !== 'object') return false; + return #slots in key || isNativeKeyObject(key); + }; getKeyObjectSlots = (key) => { if (!key || typeof key !== 'object') throw new ERR_INVALID_THIS('KeyObject'); @@ -1018,18 +1024,6 @@ function getKeyObjectAsymmetricKeyDetails(key) { return cached; } -function isKeyObject(obj) { - if (obj == null || typeof obj !== 'object') - return false; - - try { - getKeyObjectSlots(obj); - return true; - } catch { - return false; - } -} - // CryptoKey is a plain JS class whose prototype's [[Prototype]] is // Object.prototype, as Web Crypto requires. Instance storage (type enum, // extractable, algorithm, usages mask, and the KeyObject handle) lives diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index b4e3aa72292f..56d2e101eb21 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -1695,12 +1695,15 @@ void NativeKeyObject::Initialize(Environment* env, Local target) { NativeKeyObject::CreateNativeKeyObjectClass); SetMethod( env->context(), target, "getKeyObjectSlots", NativeKeyObject::GetSlots); + SetMethodNoSideEffect( + env->context(), target, "isKeyObject", NativeKeyObject::IsKeyObject); } void NativeKeyObject::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(NativeKeyObject::CreateNativeKeyObjectClass); registry->Register(NativeKeyObject::GetSlots); + registry->Register(NativeKeyObject::IsKeyObject); registry->Register(NativeKeyObject::New); } @@ -1709,6 +1712,12 @@ bool NativeKeyObject::HasInstance(Environment* env, Local value) { return !t.IsEmpty() && t->HasInstance(value); } +void NativeKeyObject::IsKeyObject(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_EQ(args.Length(), 1); + args.GetReturnValue().Set(HasInstance(env, args[0])); +} + void NativeKeyObject::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index b09756f719cf..4ed31795bb3a 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -209,6 +209,7 @@ class NativeKeyObject : public BaseObject { static void New(const v8::FunctionCallbackInfo& args); static void CreateNativeKeyObjectClass( const v8::FunctionCallbackInfo& args); + static void IsKeyObject(const v8::FunctionCallbackInfo& args); // True if `value` is a real NativeKeyObject instance. Uses the // FunctionTemplate stored on the Environment as a brand check. diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts index f532cf6a0c75..9b191f6805b8 100644 --- a/typings/internalBinding/crypto.d.ts +++ b/typings/internalBinding/crypto.d.ts @@ -940,6 +940,7 @@ export interface CryptoBinding { getExtraCACertificates(): string[]; getFipsCrypto(): 0 | 1; getHashes(): string[]; + isKeyObject(key: unknown): boolean; getKeyObjectSlots(key: object): InternalCryptoBinding.KeyObjectSlots; getOpenSSLSecLevelCrypto(): number | undefined; getSSLCiphers(): string[]; From 3d5397c0a83d732e0afc3ce7512ec9d74a709884 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 23 Aug 2026 18:30:22 +0200 Subject: [PATCH 2/2] crypto: avoid throwing CryptoKey brand checks Use the native CryptoKey FunctionTemplate brand check instead of probing slot access and catching ERR_INVALID_THIS. Keep a private-field fast path for ordinary same-realm CryptoKeys. Signed-off-by: Filip Skokan --- lib/internal/crypto/keys.js | 18 ++++++------------ src/crypto/crypto_keys.cc | 9 +++++++++ src/crypto/crypto_keys.h | 1 + typings/internalBinding/crypto.d.ts | 1 + 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 7452d956cef1..2bc63a2419fa 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -20,6 +20,7 @@ const { createCryptoKeyClass, // eslint-disable-next-line no-restricted-syntax -- intended here getCryptoKeySlots: nativeGetCryptoKeySlots, + isCryptoKey: isNativeCryptoKey, kKeyTypeSecret, kKeyTypePublic, kKeyTypePrivate, @@ -1049,6 +1050,7 @@ function getKeyObjectAsymmetricKeyDetails(key) { // requires repeat reads to return the same object so a consumer's // mutation is visible next time). let getSlots; // Populated by the createCryptoKeyClass callback below. +let isCryptoKey; const kSlotType = 0; const kSlotExtractable = 1; @@ -1150,6 +1152,10 @@ const { } static { + isCryptoKey = (key) => { + if (key == null || typeof key !== 'object') return false; + return #slots in key || isNativeCryptoKey(key); + }; getSlots = (key) => { if (!key || typeof key !== 'object') throw new ERR_INVALID_THIS('CryptoKey'); @@ -1281,18 +1287,6 @@ function getCryptoKeyHandle(key) { return getSlots(key)[kSlotHandle]; } -function isCryptoKey(obj) { - if (obj == null || typeof obj !== 'object') - return false; - - try { - getSlots(obj); - return true; - } catch { - return false; - } -} - function importGenericSecretKey( algorithm, format, diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 56d2e101eb21..2d80caf76661 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -1848,12 +1848,15 @@ void NativeCryptoKey::Initialize(Environment* env, Local target) { NativeCryptoKey::CreateCryptoKeyClass); SetMethod( env->context(), target, "getCryptoKeySlots", NativeCryptoKey::GetSlots); + SetMethodNoSideEffect( + env->context(), target, "isCryptoKey", NativeCryptoKey::IsCryptoKey); } void NativeCryptoKey::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(NativeCryptoKey::CreateCryptoKeyClass); registry->Register(NativeCryptoKey::GetSlots); + registry->Register(NativeCryptoKey::IsCryptoKey); registry->Register(NativeCryptoKey::New); } @@ -1870,6 +1873,12 @@ bool NativeCryptoKey::HasInstance(Environment* env, Local value) { return IsNativeCryptoKey(env, value); } +void NativeCryptoKey::IsCryptoKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_EQ(args.Length(), 1); + args.GetReturnValue().Set(HasInstance(env, args[0])); +} + MaybeLocal NativeCryptoKey::Create(Environment* env, const KeyObjectData& data, Local algorithm, diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 4ed31795bb3a..8fffbec60467 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -278,6 +278,7 @@ class NativeCryptoKey : public BaseObject { static void New(const v8::FunctionCallbackInfo& args); static void CreateCryptoKeyClass( const v8::FunctionCallbackInfo& args); + static void IsCryptoKey(const v8::FunctionCallbackInfo& args); static v8::MaybeLocal Create(Environment* env, const KeyObjectData& data, diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts index 9b191f6805b8..58303fe3c0f3 100644 --- a/typings/internalBinding/crypto.d.ts +++ b/typings/internalBinding/crypto.d.ts @@ -940,6 +940,7 @@ export interface CryptoBinding { getExtraCACertificates(): string[]; getFipsCrypto(): 0 | 1; getHashes(): string[]; + isCryptoKey(key: unknown): boolean; isKeyObject(key: unknown): boolean; getKeyObjectSlots(key: object): InternalCryptoBinding.KeyObjectSlots; getOpenSSLSecLevelCrypto(): number | undefined;