#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<EVPKeyPointer::AsymmetricKeyEncodingConfig> GetKeyFormatAndTypeFromJs(
    const FunctionCallbackInfo<Value>& 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<EVPKeyPointer::PKFormatType>(
        args[*offset].As<Int32>()->Value());

    if (args[*offset + 1]->IsInt32()) {
      config.type = static_cast<EVPKeyPointer::PKEncodingType>(
          args[*offset + 1].As<Int32>()->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<Value> 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<Value>());
  }

  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<Value>());
}

MaybeLocal<Value> 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<Value>();
}

MaybeLocal<Value> 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<Value>();
}

bool ExportJWKSecretKey(Environment* env,
                        const KeyObjectData& key,
                        Local<Object> target) {
  CHECK_EQ(key.GetKeyType(), kKeyTypeSecret);

  Local<Value> error;
  Local<Value> raw;
  if (!StringBytes::Encode(env->isolate(),
                           key.GetSymmetricKey(),
                           key.GetSymmetricKeySize(),
                           BASE64URL,
                           &error)
           .ToLocal(&raw)) {
    DCHECK(!error.IsEmpty());
    env->isolate()->ThrowException(error);
    return false;
  }

  return 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<Object> jwk) {
  Local<Value> 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<String>()));
}

bool ExportJWKAsymmetricKey(Environment* env,
                            const KeyObjectData& key,
                            Local<Object> 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<Object> jwk,
                                     std::string_view kty,
                                     const FunctionCallbackInfo<Value>& 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<Object> 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<double>(key.GetSymmetricKeySize() * CHAR_BIT)))
      .IsJust();
}

bool GetAsymmetricKeyDetail(Environment* env,
                            const KeyObjectData& key,
                            Local<Object> 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<const unsigned char>& 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<Value> result,
                    bool handleRsaPss) {
  return key.GetKeyType() == kKeyTypeSecret
             ? ExportJWKSecretKey(env, key, result.As<Object>())
             : ExportJWKAsymmetricKey(
                   env, key, result.As<Object>(), 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<Value>* 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<Value>* 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<EVPKeyPointer::PrivateKeyEncodingConfig>
KeyObjectData::GetPrivateKeyEncodingFromJs(
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    KeyEncodingContext context) {
  Environment* env = Environment::GetCurrent(args);

  EVPKeyPointer::PrivateKeyEncodingConfig config;
  if (!GetKeyFormatAndTypeFromJs(args, offset, context).To(&config)) {
    return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
  }

  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.ToStringView());
        if (config.cipher == nullptr) {
          THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
          return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
        }
        needs_passphrase = true;
      } else {
        CHECK(args[*offset]->IsNullOrUndefined());
        config.cipher = nullptr;
      }
      (*offset)++;
    }

    if (IsAnyBufferSource(args[*offset])) {
      CHECK_IMPLIES(context != kKeyContextInput, config.cipher != nullptr);
      ArrayBufferOrViewContents<char> passphrase(args[*offset]);
      if (!passphrase.CheckSizeInt32()) [[unlikely]] {
        THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big");
        return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
      }
      config.passphrase = passphrase.ToDataPointer();
    } else {
      CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
    }
  }

  (*offset)++;
  return Just<EVPKeyPointer::PrivateKeyEncodingConfig>(std::move(config));
}

Maybe<EVPKeyPointer::PublicKeyEncodingConfig>
KeyObjectData::GetPublicKeyEncodingFromJs(
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    KeyEncodingContext context) {
  return GetKeyFormatAndTypeFromJs(args, offset, context);
}

KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
    const v8::FunctionCallbackInfo<v8::Value>& 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<const unsigned char>{
            .data = reinterpret_cast<const unsigned char*>(key.data()),
            .len = key.size(),
        });
  }

  CHECK(args[*offset]->IsObject() && allow_key_object);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As<Object>(), KeyObjectData());
  CHECK_EQ(key->Data().GetKeyType(), kKeyTypePrivate);
  (*offset) += 4;
  return key->Data().addRef();
}

KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs(
    const FunctionCallbackInfo<Value>& args, unsigned int* offset) {
  if (IsAnyBufferSource(args[*offset])) {
    Environment* env = Environment::GetCurrent(args);
    ArrayBufferOrViewContents<char> 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<const unsigned char> buffer = {
        .data = reinterpret_cast<const unsigned char*>(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<KeyObjectHandle>(args[*offset].As<Object>());
  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<Data>(std::move(symmetric_key))) {}

KeyObjectData::KeyObjectData(KeyType type, EVPKeyPointer&& pkey)
    : key_type_(type), data_(std::make_shared<Data>(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<Mutex>();
  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<char>();
}

size_t KeyObjectData::GetSymmetricKeySize() const {
  CHECK_EQ(key_type_, kKeyTypeSecret);
  CHECK(data_);
  return data_->symmetric_key.size();
}

bool KeyObjectHandle::HasInstance(Environment* env, Local<Value> value) {
  auto t = env->crypto_key_object_handle_constructor();
  return !t.IsEmpty() && t->HasInstance(value);
}

Local<Function> 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<Object> KeyObjectHandle::Create(Environment* env,
                                           const KeyObjectData& data) {
  Local<Object> obj;
  Local<Function> 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<KeyObjectHandle>(obj);
  CHECK_NOT_NULL(key);
  key->data_ = data.addRef();
  return obj;
}

const KeyObjectData& KeyObjectHandle::Data() {
  return data_;
}

void KeyObjectHandle::New(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.IsConstructCall());
  Environment* env = Environment::GetCurrent(args);
  new KeyObjectHandle(env, args.This());
}

KeyObjectHandle::KeyObjectHandle(Environment* env,
                                 Local<Object> wrap)
    : BaseObject(env, wrap) {
  MakeWeak();
}

void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
  MarkPopErrorOnReturn mark_pop_error_on_return;

  CHECK(args[0]->IsInt32());
  KeyType type = static_cast<KeyType>(args[0].As<Uint32>()->Value());

  unsigned int offset;

  switch (type) {
  case kKeyTypeSecret: {
    CHECK_EQ(args.Length(), 2);
    ArrayBufferOrViewContents<char> 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<Value>& 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<Object> input = args[0].As<Object>();

  Local<Value> 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<Value>& 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<Value>& 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<unsigned char> key_data(args[1]);
  KeyType type = static_cast<KeyType>(args[2].As<Int32>()->Value());

  MarkPopErrorOnReturn mark_pop_error_on_return;

  typedef EVPKeyPointer (*new_key_fn)(
      int, const ncrypto::Buffer<const unsigned char>&);
  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<const unsigned char>{
                         .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<Value>& args) {
  KeyObjectHandle* self_handle;
  KeyObjectHandle* arg_handle;
  ASSIGN_OR_RETURN_UNWRAP(&self_handle, args.This());
  ASSIGN_OR_RETURN_UNWRAP(&arg_handle, args[0].As<Object>());
  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<Value>& 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<Object>())) [[likely]] {
      args.GetReturnValue().Set(args[0]);
    }
    return;
  }

  if (GetAsymmetricKeyDetail(env, data, args[0].As<Object>())) [[likely]] {
    args.GetReturnValue().Set(args[0]);
  }
}

Local<Value> 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<Value>& 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<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  args.GetReturnValue().Set(key->CheckEcKeyData());
}

void KeyObjectHandle::GetSymmetricKeySize(
    const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
  args.GetReturnValue().Set(
      static_cast<uint32_t>(key->Data().GetSymmetricKeySize()));
}

void KeyObjectHandle::Export(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  KeyType type = key->Data().GetKeyType();
  unsigned int offset = 0;

  Local<Value> 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<unsigned int>(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<unsigned int>(args.Length()));
  if (key->ExportPrivateKey(config).ToLocal(&result)) [[likely]] {
    args.GetReturnValue().Set(result);
  }
}

MaybeLocal<Value> KeyObjectHandle::ExportSecretKey() const {
  return Buffer::Copy(
             env(), data_.GetSymmetricKey(), data_.GetSymmetricKeySize())
      .FromMaybe(Local<Value>());
}

MaybeLocal<Value> KeyObjectHandle::ExportPublicKey(
    const EVPKeyPointer::PublicKeyEncodingConfig& config) const {
  return WritePublicKey(env(), data_.GetAsymmetricKey(), config);
}

MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
    const EVPKeyPointer::PrivateKeyEncodingConfig& config) const {
  return WritePrivateKey(env(), data_.GetAsymmetricKey(), config);
}

void KeyObjectHandle::ExportJWK(
    const v8::FunctionCallbackInfo<v8::Value>& 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<Object> 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<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 1);
  CHECK(args[0]->IsObject());
  KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
  CHECK_NOT_NULL(handle);
  new NativeKeyObject(env, args.This(), handle->Data());
}

void NativeKeyObject::CreateNativeKeyObjectClass(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();

  CHECK_EQ(args.Length(), 1);
  Local<Value> callback = args[0];
  CHECK(callback->IsFunction());

  Local<FunctionTemplate> t =
      NewFunctionTemplate(isolate, NativeKeyObject::New);
  t->InstanceTemplate()->SetInternalFieldCount(
      KeyObjectHandle::kInternalFieldCount);

  Local<Value> ctor;
  if (!t->GetFunction(env->context()).ToLocal(&ctor))
    return;

  Local<Value> recv = Undefined(env->isolate());
  Local<Value> ret_v;
  if (!callback.As<Function>()->Call(
          env->context(), recv, 1, &ctor).ToLocal(&ret_v)) {
    return;
  }
  Local<Array> ret = ret_v.As<Array>();
  if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return;
  env->set_crypto_key_object_secret_constructor(ctor.As<Function>());
  if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return;
  env->set_crypto_key_object_public_constructor(ctor.As<Function>());
  if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return;
  env->set_crypto_key_object_private_constructor(ctor.As<Function>());
  args.GetReturnValue().Set(ret);
}

BaseObjectPtr<BaseObject> NativeKeyObject::KeyObjectTransferData::Deserialize(
        Environment* env,
        Local<Context> context,
        std::unique_ptr<worker::TransferData> self) {
  if (context != env->context()) {
    THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
    return {};
  }

  Local<Value> handle;
  if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle))
    return {};

  Local<Function> key_ctor;
  Local<Value> 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<Value> key;
  if (!key_ctor->NewInstance(context, 1, &handle).ToLocal(&key))
    return {};

  return BaseObjectPtr<BaseObject>(Unwrap<KeyObjectHandle>(key.As<Object>()));
}

BaseObject::TransferMode NativeKeyObject::GetTransferMode() const {
  return BaseObject::TransferMode::kCloneable;
}

std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
    const {
  return std::make_unique<KeyObjectTransferData>(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<Object> target) {
  target->Set(env->context(),
              FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"),
              KeyObjectHandle::Initialize(env)).Check();

  constexpr int kKeyEncodingPKCS1 =
      static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS1);
  constexpr int kKeyEncodingPKCS8 =
      static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS8);
  constexpr int kKeyEncodingSPKI =
      static_cast<int>(EVPKeyPointer::PKEncodingType::SPKI);
  constexpr int kKeyEncodingSEC1 =
      static_cast<int>(EVPKeyPointer::PKEncodingType::SEC1);
  constexpr int kKeyFormatDER =
      static_cast<int>(EVPKeyPointer::PKFormatType::DER);
  constexpr int kKeyFormatPEM =
      static_cast<int>(EVPKeyPointer::PKFormatType::PEM);
  constexpr int kKeyFormatJWK =
      static_cast<int>(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
