#include "crypto/crypto_keys.h" #include "crypto/crypto_common.h" #include "crypto/crypto_dsa.h" #include "crypto/crypto_ec.h" #include "crypto/crypto_dh.h" #include "crypto/crypto_rsa.h" #include "crypto/crypto_util.h" #include "async_wrap-inl.h" #include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node.h" #include "node_buffer.h" #include "string_bytes.h" #include "threadpoolwork-inl.h" #include "util-inl.h" #include "v8.h" namespace node { using ncrypto::BIOPointer; using ncrypto::ECKeyPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using ncrypto::MarkPopErrorOnReturn; using ncrypto::PKCS8Pointer; using v8::Array; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Int32; using v8::Isolate; using v8::Just; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::NewStringType; using v8::Nothing; using v8::Number; using v8::Object; using v8::String; using v8::Uint32; using v8::Undefined; using v8::Value; namespace crypto { namespace { Maybe GetKeyFormatAndTypeFromJs( const FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context) { EVPKeyPointer::AsymmetricKeyEncodingConfig config; // During key pair generation, it is possible not to specify a key encoding, // which will lead to a key object being returned. if (args[*offset]->IsUndefined()) { CHECK_EQ(context, kKeyContextGenerate); CHECK(args[*offset + 1]->IsUndefined()); config.output_key_object = true; } else { config.output_key_object = false; CHECK(args[*offset]->IsInt32()); config.format = static_cast( args[*offset].As()->Value()); if (args[*offset + 1]->IsInt32()) { config.type = static_cast( args[*offset + 1].As()->Value()); } else { CHECK((context == kKeyContextInput && config.format == EVPKeyPointer::PKFormatType::PEM) || (context == kKeyContextGenerate && config.format == EVPKeyPointer::PKFormatType::JWK)); CHECK(args[*offset + 1]->IsNullOrUndefined()); config.type = EVPKeyPointer::PKEncodingType::PKCS1; } } *offset += 2; return Just(config); } MaybeLocal ToV8Value( Environment* env, const BIOPointer& bio, const EVPKeyPointer::AsymmetricKeyEncodingConfig& config) { if (!bio) return {}; BUF_MEM* bptr = bio; if (config.format == EVPKeyPointer::PKFormatType::PEM) { // PEM is an ASCII format, so we will return it as a string. return String::NewFromUtf8( env->isolate(), bptr->data, NewStringType::kNormal, bptr->length) .FromMaybe(Local()); } CHECK_EQ(config.format, EVPKeyPointer::PKFormatType::DER); // DER is binary, return it as a buffer. return Buffer::Copy(env, bptr->data, bptr->length).FromMaybe(Local()); } MaybeLocal WritePrivateKey( Environment* env, const EVPKeyPointer& pkey, const EVPKeyPointer::PrivateKeyEncodingConfig& config) { if (!pkey) return {}; auto res = pkey.writePrivateKey(config); if (res) return ToV8Value(env, std::move(res.value), config); ThrowCryptoError( env, res.openssl_error.value_or(0), "Failed to encode private key"); return MaybeLocal(); } MaybeLocal WritePublicKey( Environment* env, const EVPKeyPointer& pkey, const EVPKeyPointer::PublicKeyEncodingConfig& config) { if (!pkey) return {}; auto res = pkey.writePublicKey(config); if (res) return ToV8Value(env, res.value, config); ThrowCryptoError( env, res.openssl_error.value_or(0), "Failed to encode public key"); return MaybeLocal(); } bool ExportJWKSecretKey(Environment* env, const KeyObjectData& key, Local target) { CHECK_EQ(key.GetKeyType(), kKeyTypeSecret); Local raw; return StringBytes::Encode(env->isolate(), key.GetSymmetricKey(), key.GetSymmetricKeySize(), BASE64URL) .ToLocal(&raw) && target ->Set(env->context(), env->jwk_kty_string(), env->jwk_oct_string()) .IsJust() && target->Set(env->context(), env->jwk_k_string(), raw).IsJust(); } KeyObjectData ImportJWKSecretKey(Environment* env, Local jwk) { Local key; if (!jwk->Get(env->context(), env->jwk_k_string()).ToLocal(&key) || !key->IsString()) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK secret key format"); return {}; } static_assert(String::kMaxLength <= INT_MAX); return KeyObjectData::CreateSecret( ByteSource::FromEncodedString(env, key.As())); } bool ExportJWKAsymmetricKey(Environment* env, const KeyObjectData& key, Local target, bool handleRsaPss) { switch (key.GetAsymmetricKey().id()) { case EVP_PKEY_RSA_PSS: { if (handleRsaPss) return ExportJWKRsaKey(env, key, target); break; } case EVP_PKEY_RSA: return ExportJWKRsaKey(env, key, target); case EVP_PKEY_EC: return ExportJWKEcKey(env, key, target); case EVP_PKEY_ED25519: // Fall through case EVP_PKEY_ED448: // Fall through case EVP_PKEY_X25519: // Fall through case EVP_PKEY_X448: return ExportJWKEdKey(env, key, target); } THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env); return false; } KeyObjectData ImportJWKAsymmetricKey(Environment* env, Local jwk, std::string_view kty, const FunctionCallbackInfo& args, unsigned int offset) { if (kty == "RSA") { return ImportJWKRsaKey(env, jwk, args, offset); } else if (kty == "EC") { return ImportJWKEcKey(env, jwk, args, offset); } THROW_ERR_CRYPTO_INVALID_JWK( env, "%s is not a supported JWK key type", kty.data()); return {}; } bool GetSecretKeyDetail(Environment* env, const KeyObjectData& key, Local target) { // For the secret key detail, all we care about is the length, // converted to bits. return target ->Set(env->context(), env->length_string(), Number::New( env->isolate(), static_cast(key.GetSymmetricKeySize() * CHAR_BIT))) .IsJust(); } bool GetAsymmetricKeyDetail(Environment* env, const KeyObjectData& key, Local target) { if (!key) { THROW_ERR_CRYPTO_OPERATION_FAILED(env); return false; } switch (key.GetAsymmetricKey().id()) { case EVP_PKEY_RSA: // Fall through case EVP_PKEY_RSA2: // Fall through case EVP_PKEY_RSA_PSS: return GetRsaKeyDetail(env, key, target); case EVP_PKEY_DSA: return GetDsaKeyDetail(env, key, target); case EVP_PKEY_EC: return GetEcKeyDetail(env, key, target); case EVP_PKEY_DH: return GetDhKeyDetail(env, key, target); } THROW_ERR_CRYPTO_INVALID_KEYTYPE(env); return false; } KeyObjectData TryParsePrivateKey( Environment* env, const EVPKeyPointer::PrivateKeyEncodingConfig& config, const ncrypto::Buffer& buffer) { auto res = EVPKeyPointer::TryParsePrivateKey(config, buffer); if (res) { return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate, std::move(res.value)); } if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { THROW_ERR_MISSING_PASSPHRASE(env, "Passphrase required for encrypted key"); } else { ThrowCryptoError( env, res.openssl_error.value_or(0), "Failed to read private key"); } return {}; } bool ExportJWKInner(Environment* env, const KeyObjectData& key, Local result, bool handleRsaPss) { return key.GetKeyType() == kKeyTypeSecret ? ExportJWKSecretKey(env, key, result.As()) : ExportJWKAsymmetricKey( env, key, result.As(), handleRsaPss); } int GetOKPCurveFromName(const char* name) { int nid; if (strcmp(name, "Ed25519") == 0) { nid = EVP_PKEY_ED25519; } else if (strcmp(name, "Ed448") == 0) { nid = EVP_PKEY_ED448; } else if (strcmp(name, "X25519") == 0) { nid = EVP_PKEY_X25519; } else if (strcmp(name, "X448") == 0) { nid = EVP_PKEY_X448; } else { nid = NID_undef; } return nid; } } // namespace bool KeyObjectData::ToEncodedPublicKey( Environment* env, const EVPKeyPointer::PublicKeyEncodingConfig& config, Local* out) { CHECK(key_type_ != KeyType::kKeyTypeSecret); if (config.output_key_object) { // Note that this has the downside of containing sensitive data of the // private key. return KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic)) .ToLocal(out); } else if (config.format == EVPKeyPointer::PKFormatType::JWK) { *out = Object::New(env->isolate()); return ExportJWKInner( env, addRefWithType(KeyType::kKeyTypePublic), *out, false); } return WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out); } bool KeyObjectData::ToEncodedPrivateKey( Environment* env, const EVPKeyPointer::PrivateKeyEncodingConfig& config, Local* out) { CHECK(key_type_ != KeyType::kKeyTypeSecret); if (config.output_key_object) { return KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePrivate)) .ToLocal(out); } else if (config.format == EVPKeyPointer::PKFormatType::JWK) { *out = Object::New(env->isolate()); return ExportJWKInner( env, addRefWithType(KeyType::kKeyTypePrivate), *out, false); } return WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out); } Maybe KeyObjectData::GetPrivateKeyEncodingFromJs( const FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context) { Environment* env = Environment::GetCurrent(args); EVPKeyPointer::PrivateKeyEncodingConfig config; if (!GetKeyFormatAndTypeFromJs(args, offset, context).To(&config)) { return Nothing(); } if (config.output_key_object) { if (context != kKeyContextInput) (*offset)++; } else { bool needs_passphrase = false; if (context != kKeyContextInput) { if (args[*offset]->IsString()) { Utf8Value cipher_name(env->isolate(), args[*offset]); config.cipher = ncrypto::getCipherByName(*cipher_name); if (config.cipher == nullptr) { THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); return Nothing(); } needs_passphrase = true; } else { CHECK(args[*offset]->IsNullOrUndefined()); config.cipher = nullptr; } (*offset)++; } if (IsAnyBufferSource(args[*offset])) { CHECK_IMPLIES(context != kKeyContextInput, config.cipher != nullptr); ArrayBufferOrViewContents passphrase(args[*offset]); if (!passphrase.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big"); return Nothing(); } config.passphrase = passphrase.ToDataPointer(); } else { CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase); } } (*offset)++; return Just(std::move(config)); } Maybe KeyObjectData::GetPublicKeyEncodingFromJs( const FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context) { return GetKeyFormatAndTypeFromJs(args, offset, context); } KeyObjectData KeyObjectData::GetPrivateKeyFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset, bool allow_key_object) { if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) { Environment* env = Environment::GetCurrent(args); auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); EVPKeyPointer::PrivateKeyEncodingConfig config; if (!GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput) .To(&config)) { return {}; } return TryParsePrivateKey( env, config, ncrypto::Buffer{ .data = reinterpret_cast(key.data()), .len = key.size(), }); } CHECK(args[*offset]->IsObject() && allow_key_object); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As(), KeyObjectData()); CHECK_EQ(key->Data().GetKeyType(), kKeyTypePrivate); (*offset) += 4; return key->Data().addRef(); } KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs( const FunctionCallbackInfo& args, unsigned int* offset) { if (IsAnyBufferSource(args[*offset])) { Environment* env = Environment::GetCurrent(args); ArrayBufferOrViewContents data(args[(*offset)++]); if (!data.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "keyData is too big"); return {}; } EVPKeyPointer::PrivateKeyEncodingConfig config; if (!KeyObjectData::GetPrivateKeyEncodingFromJs( args, offset, kKeyContextInput) .To(&config)) { return {}; } ncrypto::Buffer buffer = { .data = reinterpret_cast(data.data()), .len = data.size(), }; if (config.format == EVPKeyPointer::PKFormatType::PEM) { // For PEM, we can easily determine whether it is a public or private key // by looking for the respective PEM tags. auto res = EVPKeyPointer::TryParsePublicKeyPEM(buffer); if (res) { return CreateAsymmetric(kKeyTypePublic, std::move(res.value)); } if (res.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) { return TryParsePrivateKey(env, config, buffer); } ThrowCryptoError( env, res.openssl_error.value_or(0), "Failed to read asymmetric key"); return {}; } // For DER, the type determines how to parse it. SPKI, PKCS#8 and SEC1 are // easy, but PKCS#1 can be a public key or a private key. static const auto is_public = [](const auto& config, const auto& buffer) -> bool { switch (config.type) { case EVPKeyPointer::PKEncodingType::PKCS1: return !EVPKeyPointer::IsRSAPrivateKey(buffer); case EVPKeyPointer::PKEncodingType::SPKI: return true; case EVPKeyPointer::PKEncodingType::PKCS8: return false; case EVPKeyPointer::PKEncodingType::SEC1: return false; default: UNREACHABLE("Invalid key encoding type"); } }; if (is_public(config, buffer)) { auto res = EVPKeyPointer::TryParsePublicKey(config, buffer); if (res) { return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value)); } ThrowCryptoError( env, res.openssl_error.value_or(0), "Failed to read asymmetric key"); return {}; } return TryParsePrivateKey(env, config, buffer); } CHECK(args[*offset]->IsObject()); KeyObjectHandle* key = BaseObject::Unwrap(args[*offset].As()); CHECK_NOT_NULL(key); CHECK_NE(key->Data().GetKeyType(), kKeyTypeSecret); (*offset) += 4; return key->Data().addRef(); } KeyObjectData KeyObjectData::GetParsedKey(KeyType type, Environment* env, EVPKeyPointer&& pkey, ParseKeyResult ret, const char* default_msg) { MarkPopErrorOnReturn mark_pop_error_on_return; switch (ret) { case ParseKeyResult::kParseKeyOk: { return CreateAsymmetric(type, std::move(pkey)); } case ParseKeyResult::kParseKeyNeedPassphrase: { THROW_ERR_MISSING_PASSPHRASE(env, "Passphrase required for encrypted key"); return {}; } default: { ThrowCryptoError(env, mark_pop_error_on_return.peekError(), default_msg); return {}; } } } KeyObjectData::KeyObjectData(std::nullptr_t) : key_type_(KeyType::kKeyTypeSecret) {} KeyObjectData::KeyObjectData(ByteSource symmetric_key) : key_type_(KeyType::kKeyTypeSecret), data_(std::make_shared(std::move(symmetric_key))) {} KeyObjectData::KeyObjectData(KeyType type, EVPKeyPointer&& pkey) : key_type_(type), data_(std::make_shared(std::move(pkey))) {} void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const { if (!*this) return; switch (GetKeyType()) { case kKeyTypeSecret: { if (data_->symmetric_key) { tracker->TrackFieldWithSize("symmetric_key", data_->symmetric_key.size()); } break; } case kKeyTypePrivate: // Fall through case kKeyTypePublic: { if (data_->asymmetric_key) { tracker->TrackFieldWithSize( "key", kSizeOf_EVP_PKEY + data_->asymmetric_key.rawPublicKeySize() + data_->asymmetric_key.rawPrivateKeySize()); } break; } default: UNREACHABLE(); } } Mutex& KeyObjectData::mutex() const { if (!mutex_) mutex_ = std::make_shared(); return *mutex_.get(); } KeyObjectData KeyObjectData::CreateSecret(ByteSource key) { return KeyObjectData(std::move(key)); } KeyObjectData KeyObjectData::CreateAsymmetric(KeyType key_type, EVPKeyPointer&& pkey) { CHECK(pkey); return KeyObjectData(key_type, std::move(pkey)); } KeyType KeyObjectData::GetKeyType() const { CHECK(data_); return key_type_; } const EVPKeyPointer& KeyObjectData::GetAsymmetricKey() const { CHECK_NE(key_type_, kKeyTypeSecret); CHECK(data_); return data_->asymmetric_key; } const char* KeyObjectData::GetSymmetricKey() const { CHECK_EQ(key_type_, kKeyTypeSecret); CHECK(data_); return data_->symmetric_key.data(); } size_t KeyObjectData::GetSymmetricKeySize() const { CHECK_EQ(key_type_, kKeyTypeSecret); CHECK(data_); return data_->symmetric_key.size(); } bool KeyObjectHandle::HasInstance(Environment* env, Local value) { auto t = env->crypto_key_object_handle_constructor(); return !t.IsEmpty() && t->HasInstance(value); } Local KeyObjectHandle::Initialize(Environment* env) { auto templ = env->crypto_key_object_handle_constructor(); if (templ.IsEmpty()) { Isolate* isolate = env->isolate(); templ = NewFunctionTemplate(isolate, New); templ->InstanceTemplate()->SetInternalFieldCount( KeyObjectHandle::kInternalFieldCount); SetProtoMethod(isolate, templ, "init", Init); SetProtoMethodNoSideEffect( isolate, templ, "getSymmetricKeySize", GetSymmetricKeySize); SetProtoMethodNoSideEffect( isolate, templ, "getAsymmetricKeyType", GetAsymmetricKeyType); SetProtoMethodNoSideEffect( isolate, templ, "checkEcKeyData", CheckEcKeyData); SetProtoMethod(isolate, templ, "export", Export); SetProtoMethod(isolate, templ, "exportJwk", ExportJWK); SetProtoMethod(isolate, templ, "initECRaw", InitECRaw); SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw); SetProtoMethod(isolate, templ, "initJwk", InitJWK); SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail); SetProtoMethod(isolate, templ, "equals", Equals); env->set_crypto_key_object_handle_constructor(templ); } return templ->GetFunction(env->context()).ToLocalChecked(); } void KeyObjectHandle::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(New); registry->Register(Init); registry->Register(GetSymmetricKeySize); registry->Register(GetAsymmetricKeyType); registry->Register(CheckEcKeyData); registry->Register(Export); registry->Register(ExportJWK); registry->Register(InitECRaw); registry->Register(InitEDRaw); registry->Register(InitJWK); registry->Register(GetKeyDetail); registry->Register(Equals); } MaybeLocal KeyObjectHandle::Create(Environment* env, const KeyObjectData& data) { Local obj; Local ctor = KeyObjectHandle::Initialize(env); CHECK(!env->crypto_key_object_handle_constructor().IsEmpty()); if (!ctor->NewInstance(env->context(), 0, nullptr).ToLocal(&obj)) { return {}; } KeyObjectHandle* key = Unwrap(obj); CHECK_NOT_NULL(key); key->data_ = data.addRef(); return obj; } const KeyObjectData& KeyObjectHandle::Data() { return data_; } void KeyObjectHandle::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); new KeyObjectHandle(env, args.This()); } KeyObjectHandle::KeyObjectHandle(Environment* env, Local wrap) : BaseObject(env, wrap) { MakeWeak(); } void KeyObjectHandle::Init(const FunctionCallbackInfo& args) { KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); MarkPopErrorOnReturn mark_pop_error_on_return; CHECK(args[0]->IsInt32()); KeyType type = static_cast(args[0].As()->Value()); unsigned int offset; switch (type) { case kKeyTypeSecret: { CHECK_EQ(args.Length(), 2); ArrayBufferOrViewContents buf(args[1]); key->data_ = KeyObjectData::CreateSecret(buf.ToCopy()); break; } case kKeyTypePublic: { CHECK_EQ(args.Length(), 5); offset = 1; auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset); if (!data) return; key->data_ = data.addRefWithType(kKeyTypePublic); break; } case kKeyTypePrivate: { CHECK_EQ(args.Length(), 5); offset = 1; if (auto data = KeyObjectData::GetPrivateKeyFromJs(args, &offset, false)) { key->data_ = std::move(data); } break; } default: UNREACHABLE(); } } void KeyObjectHandle::InitJWK(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); MarkPopErrorOnReturn mark_pop_error_on_return; // The argument must be a JavaScript object that we will inspect // to get the JWK properties from. CHECK(args[0]->IsObject()); // Step one, Secret key or not? Local input = args[0].As(); Local kty; if (!input->Get(env->context(), env->jwk_kty_string()).ToLocal(&kty) || !kty->IsString()) { return THROW_ERR_CRYPTO_INVALID_JWK(env); } Utf8Value kty_string(env->isolate(), kty); if (kty_string == "oct") { // Secret key key->data_ = ImportJWKSecretKey(env, input); if (!key->data_) { // ImportJWKSecretKey is responsible for throwing an appropriate error return; } } else { key->data_ = ImportJWKAsymmetricKey(env, input, *kty_string, args, 1); if (!key->data_) { // ImportJWKAsymmetricKey is responsible for throwing an appropriate error return; } } args.GetReturnValue().Set(key->data_.GetKeyType()); } void KeyObjectHandle::InitECRaw(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); CHECK(args[0]->IsString()); Utf8Value name(env->isolate(), args[0]); MarkPopErrorOnReturn mark_pop_error_on_return; int id = OBJ_txt2nid(*name); auto eckey = ECKeyPointer::NewByCurveName(id); if (!eckey) return args.GetReturnValue().Set(false); const auto group = eckey.getGroup(); auto pub = ECDH::BufferToPoint(env, group, args[1]); if (!pub || !eckey || !eckey.setPublicKey(pub)) { return args.GetReturnValue().Set(false); } auto pkey = EVPKeyPointer::New(); if (!pkey.assign(eckey)) { args.GetReturnValue().Set(false); } eckey.release(); // Release ownership of the key key->data_ = KeyObjectData::CreateAsymmetric(kKeyTypePublic, std::move(pkey)); args.GetReturnValue().Set(true); } void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); CHECK(args[0]->IsString()); Utf8Value name(env->isolate(), args[0]); ArrayBufferOrViewContents key_data(args[1]); KeyType type = static_cast(args[2].As()->Value()); MarkPopErrorOnReturn mark_pop_error_on_return; typedef EVPKeyPointer (*new_key_fn)( int, const ncrypto::Buffer&); new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate : EVPKeyPointer::NewRawPublic; int id = GetOKPCurveFromName(*name); switch (id) { case EVP_PKEY_X25519: case EVP_PKEY_X448: case EVP_PKEY_ED25519: case EVP_PKEY_ED448: { auto pkey = fn(id, ncrypto::Buffer{ .data = key_data.data(), .len = key_data.size(), }); if (!pkey) { return args.GetReturnValue().Set(false); } key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); CHECK(key->data_); break; } default: UNREACHABLE(); } args.GetReturnValue().Set(true); } void KeyObjectHandle::Equals(const FunctionCallbackInfo& args) { KeyObjectHandle* self_handle; KeyObjectHandle* arg_handle; ASSIGN_OR_RETURN_UNWRAP(&self_handle, args.This()); ASSIGN_OR_RETURN_UNWRAP(&arg_handle, args[0].As()); const auto& key = self_handle->Data(); const auto& key2 = arg_handle->Data(); KeyType key_type = key.GetKeyType(); CHECK_EQ(key_type, key2.GetKeyType()); bool ret; switch (key_type) { case kKeyTypeSecret: { size_t size = key.GetSymmetricKeySize(); if (size == key2.GetSymmetricKeySize()) { ret = CRYPTO_memcmp( key.GetSymmetricKey(), key2.GetSymmetricKey(), size) == 0; } else { ret = false; } break; } case kKeyTypePublic: // Fall through case kKeyTypePrivate: { EVP_PKEY* pkey = key.GetAsymmetricKey().get(); EVP_PKEY* pkey2 = key2.GetAsymmetricKey().get(); #if OPENSSL_VERSION_MAJOR >= 3 int ok = EVP_PKEY_eq(pkey, pkey2); #else int ok = EVP_PKEY_cmp(pkey, pkey2); #endif if (ok == -2) { Environment* env = Environment::GetCurrent(args); return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); } ret = ok == 1; break; } default: UNREACHABLE("unsupported key type"); } args.GetReturnValue().Set(ret); } void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); CHECK(args[0]->IsObject()); const auto& data = key->Data(); if (data.GetKeyType() == kKeyTypeSecret) { if (GetSecretKeyDetail(env, data, args[0].As())) [[likely]] { args.GetReturnValue().Set(args[0]); } return; } if (GetAsymmetricKeyDetail(env, data, args[0].As())) [[likely]] { args.GetReturnValue().Set(args[0]); } } Local KeyObjectHandle::GetAsymmetricKeyType() const { switch (data_.GetAsymmetricKey().id()) { case EVP_PKEY_RSA: return env()->crypto_rsa_string(); case EVP_PKEY_RSA_PSS: return env()->crypto_rsa_pss_string(); case EVP_PKEY_DSA: return env()->crypto_dsa_string(); case EVP_PKEY_DH: return env()->crypto_dh_string(); case EVP_PKEY_EC: return env()->crypto_ec_string(); case EVP_PKEY_ED25519: return env()->crypto_ed25519_string(); case EVP_PKEY_ED448: return env()->crypto_ed448_string(); case EVP_PKEY_X25519: return env()->crypto_x25519_string(); case EVP_PKEY_X448: return env()->crypto_x448_string(); default: return Undefined(env()->isolate()); } } void KeyObjectHandle::GetAsymmetricKeyType( const FunctionCallbackInfo& args) { KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); args.GetReturnValue().Set(key->GetAsymmetricKeyType()); } bool KeyObjectHandle::CheckEcKeyData() const { MarkPopErrorOnReturn mark_pop_error_on_return; const auto& key = data_.GetAsymmetricKey(); EVPKeyCtxPointer ctx = key.newCtx(); CHECK(ctx); CHECK_EQ(key.id(), EVP_PKEY_EC); return data_.GetKeyType() == kKeyTypePrivate ? ctx.privateCheck() : ctx.publicCheck(); } void KeyObjectHandle::CheckEcKeyData(const FunctionCallbackInfo& args) { KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); args.GetReturnValue().Set(key->CheckEcKeyData()); } void KeyObjectHandle::GetSymmetricKeySize( const FunctionCallbackInfo& args) { KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); args.GetReturnValue().Set( static_cast(key->Data().GetSymmetricKeySize())); } void KeyObjectHandle::Export(const FunctionCallbackInfo& args) { KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); KeyType type = key->Data().GetKeyType(); unsigned int offset = 0; Local result; if (type == kKeyTypeSecret) { if (key->ExportSecretKey().ToLocal(&result)) [[likely]] { args.GetReturnValue().Set(result); } return; } if (type == kKeyTypePublic) { EVPKeyPointer::PublicKeyEncodingConfig config; if (!KeyObjectData::GetPublicKeyEncodingFromJs( args, &offset, kKeyContextExport) .To(&config)) { return; } CHECK_EQ(offset, static_cast(args.Length())); if (key->ExportPublicKey(config).ToLocal(&result)) [[likely]] { args.GetReturnValue().Set(result); } return; } CHECK_EQ(type, kKeyTypePrivate); EVPKeyPointer::PrivateKeyEncodingConfig config; if (!KeyObjectData::GetPrivateKeyEncodingFromJs( args, &offset, kKeyContextExport) .To(&config)) { return; } CHECK_EQ(offset, static_cast(args.Length())); if (key->ExportPrivateKey(config).ToLocal(&result)) [[likely]] { args.GetReturnValue().Set(result); } } MaybeLocal KeyObjectHandle::ExportSecretKey() const { return Buffer::Copy( env(), data_.GetSymmetricKey(), data_.GetSymmetricKeySize()) .FromMaybe(Local()); } MaybeLocal KeyObjectHandle::ExportPublicKey( const EVPKeyPointer::PublicKeyEncodingConfig& config) const { return WritePublicKey(env(), data_.GetAsymmetricKey(), config); } MaybeLocal KeyObjectHandle::ExportPrivateKey( const EVPKeyPointer::PrivateKeyEncodingConfig& config) const { return WritePrivateKey(env(), data_.GetAsymmetricKey(), config); } void KeyObjectHandle::ExportJWK( const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); CHECK(args[0]->IsObject()); CHECK(args[1]->IsBoolean()); if (ExportJWKInner(env, key->Data(), args[0], args[1]->IsTrue())) { args.GetReturnValue().Set(args[0]); } } void NativeKeyObject::Initialize(Environment* env, Local target) { SetMethod(env->context(), target, "createNativeKeyObjectClass", NativeKeyObject::CreateNativeKeyObjectClass); } void NativeKeyObject::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(NativeKeyObject::CreateNativeKeyObjectClass); registry->Register(NativeKeyObject::New); } void NativeKeyObject::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsObject()); KeyObjectHandle* handle = Unwrap(args[0].As()); CHECK_NOT_NULL(handle); new NativeKeyObject(env, args.This(), handle->Data()); } void NativeKeyObject::CreateNativeKeyObjectClass( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); CHECK_EQ(args.Length(), 1); Local callback = args[0]; CHECK(callback->IsFunction()); Local t = NewFunctionTemplate(isolate, NativeKeyObject::New); t->InstanceTemplate()->SetInternalFieldCount( KeyObjectHandle::kInternalFieldCount); Local ctor; if (!t->GetFunction(env->context()).ToLocal(&ctor)) return; Local recv = Undefined(env->isolate()); Local ret_v; if (!callback.As()->Call( env->context(), recv, 1, &ctor).ToLocal(&ret_v)) { return; } Local ret = ret_v.As(); if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return; env->set_crypto_key_object_secret_constructor(ctor.As()); if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return; env->set_crypto_key_object_public_constructor(ctor.As()); if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return; env->set_crypto_key_object_private_constructor(ctor.As()); args.GetReturnValue().Set(ret); } BaseObjectPtr NativeKeyObject::KeyObjectTransferData::Deserialize( Environment* env, Local context, std::unique_ptr self) { if (context != env->context()) { THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env); return {}; } Local handle; if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle)) return {}; Local key_ctor; Local arg = FIXED_ONE_BYTE_STRING(env->isolate(), "internal/crypto/keys"); if (env->builtin_module_require() ->Call(context, Null(env->isolate()), 1, &arg) .IsEmpty()) { return {}; } switch (data_.GetKeyType()) { case kKeyTypeSecret: key_ctor = env->crypto_key_object_secret_constructor(); break; case kKeyTypePublic: key_ctor = env->crypto_key_object_public_constructor(); break; case kKeyTypePrivate: key_ctor = env->crypto_key_object_private_constructor(); break; default: UNREACHABLE(); } Local key; if (!key_ctor->NewInstance(context, 1, &handle).ToLocal(&key)) return {}; return BaseObjectPtr(Unwrap(key.As())); } BaseObject::TransferMode NativeKeyObject::GetTransferMode() const { return BaseObject::TransferMode::kCloneable; } std::unique_ptr NativeKeyObject::CloneForMessaging() const { return std::make_unique(handle_data_); } WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data, ByteSource* out) { CHECK_EQ(key_data.GetKeyType(), kKeyTypePublic); Mutex::ScopedLock lock(key_data.mutex()); auto bio = key_data.GetAsymmetricKey().derPublicKey(); if (!bio) return WebCryptoKeyExportStatus::FAILED; *out = ByteSource::FromBIO(bio); return WebCryptoKeyExportStatus::OK; } WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data, ByteSource* out) { CHECK_EQ(key_data.GetKeyType(), kKeyTypePrivate); Mutex::ScopedLock lock(key_data.mutex()); const auto& m_pkey = key_data.GetAsymmetricKey(); auto bio = BIOPointer::NewMem(); CHECK(bio); PKCS8Pointer p8inf(EVP_PKEY2PKCS8(m_pkey.get())); if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), p8inf.get())) return WebCryptoKeyExportStatus::FAILED; *out = ByteSource::FromBIO(bio); return WebCryptoKeyExportStatus::OK; } namespace Keys { void Initialize(Environment* env, Local target) { target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"), KeyObjectHandle::Initialize(env)).Check(); constexpr int kKeyEncodingPKCS1 = static_cast(EVPKeyPointer::PKEncodingType::PKCS1); constexpr int kKeyEncodingPKCS8 = static_cast(EVPKeyPointer::PKEncodingType::PKCS8); constexpr int kKeyEncodingSPKI = static_cast(EVPKeyPointer::PKEncodingType::SPKI); constexpr int kKeyEncodingSEC1 = static_cast(EVPKeyPointer::PKEncodingType::SEC1); constexpr int kKeyFormatDER = static_cast(EVPKeyPointer::PKFormatType::DER); constexpr int kKeyFormatPEM = static_cast(EVPKeyPointer::PKFormatType::PEM); constexpr int kKeyFormatJWK = static_cast(EVPKeyPointer::PKFormatType::JWK); constexpr auto kSigEncDER = DSASigEnc::DER; constexpr auto kSigEncP1363 = DSASigEnc::P1363; NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatRaw); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448); NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448); NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1); NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8); NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI); NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1); NODE_DEFINE_CONSTANT(target, kKeyFormatDER); NODE_DEFINE_CONSTANT(target, kKeyFormatPEM); NODE_DEFINE_CONSTANT(target, kKeyFormatJWK); NODE_DEFINE_CONSTANT(target, kKeyTypeSecret); NODE_DEFINE_CONSTANT(target, kKeyTypePublic); NODE_DEFINE_CONSTANT(target, kKeyTypePrivate); NODE_DEFINE_CONSTANT(target, kSigEncDER); NODE_DEFINE_CONSTANT(target, kSigEncP1363); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { KeyObjectHandle::RegisterExternalReferences(registry); } } // namespace Keys } // namespace crypto } // namespace node