#include "crypto/crypto_x509.h"
#include "base_object-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node_errors.h"
#include "util-inl.h"
#include "v8.h"

#include <string>
#include <vector>

namespace node {

using ncrypto::BignumPointer;
using ncrypto::BIOPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::DataPointer;
using ncrypto::Digest;
using ncrypto::SSLPointer;
using ncrypto::X509Name;
using ncrypto::X509Pointer;
using ncrypto::X509View;
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::Boolean;
using v8::Context;
using v8::Date;
using v8::DictionaryTemplate;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::LocalVector;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Undefined;
using v8::Value;

namespace crypto {

ManagedX509::ManagedX509(X509Pointer&& cert) : cert_(std::move(cert)) {}

ManagedX509::ManagedX509(const ManagedX509& that) {
  *this = that;
}

ManagedX509& ManagedX509::operator=(const ManagedX509& that) {
  cert_.reset(that.get());
  if (cert_) [[likely]]
    X509_up_ref(cert_.get());
  return *this;
}

void ManagedX509::MemoryInfo(MemoryTracker* tracker) const {
  if (!cert_) return;
  // This is an approximation based on the der encoding size.
  int size = i2d_X509(cert_.get(), nullptr);
  tracker->TrackFieldWithSize("cert", size);
}

namespace {
MaybeLocal<Value> GetFingerprintDigest(Environment* env,
                                       const Digest& method,
                                       const X509View& cert) {
  auto fingerprint = cert.getFingerprint(method);
  // Returning an empty string indicates that the digest failed for
  // some reason.
  if (!fingerprint.has_value()) [[unlikely]] {
    return Undefined(env->isolate());
  }
  auto& fp = fingerprint.value();
  return OneByteString(env->isolate(), fp.data(), fp.length());
}

template <const ncrypto::Digest& algo>
void Fingerprint(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetFingerprintDigest(env, algo, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

MaybeLocal<String> ToV8Value(Environment* env, std::string_view val) {
  return String::NewFromUtf8(
      env->isolate(), val.data(), NewStringType::kNormal, val.size());
}

MaybeLocal<Value> ToV8Value(Local<Context> context, BIOPointer&& bio) {
  if (!bio) [[unlikely]]
    return {};
  BUF_MEM* mem = bio;
  if (!mem) [[unlikely]]
    return {};
  Local<Value> ret;
  if (!String::NewFromUtf8(context->GetIsolate(),
                           mem->data,
                           NewStringType::kNormal,
                           mem->length)
           .ToLocal(&ret))
    return {};
  return ret;
}

MaybeLocal<Value> ToV8Value(Local<Context> context, const BIOPointer& bio) {
  if (!bio) [[unlikely]]
    return {};
  BUF_MEM* mem = bio;
  if (!mem) [[unlikely]]
    return {};
  Local<Value> ret;
  if (!String::NewFromUtf8(context->GetIsolate(),
                           mem->data,
                           NewStringType::kNormal,
                           mem->length)
           .ToLocal(&ret))
    return {};
  return ret;
}

MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
  if (bio == nullptr || !*bio) [[unlikely]]
    return {};
  BUF_MEM* mem = *bio;
  if (!mem) [[unlikely]]
    return {};
#ifdef V8_ENABLE_SANDBOX
  // If the v8 sandbox is enabled, then all array buffers must be allocated
  // via the isolate. External buffers are not allowed. So, instead of wrapping
  // the BIOPointer we'll copy it instead.
  auto backing = ArrayBuffer::NewBackingStore(
      env->isolate(),
      mem->length,
      BackingStoreInitializationMode::kUninitialized,
      BackingStoreOnFailureMode::kReturnNull);
  if (!backing) {
    THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
    return MaybeLocal<Value>();
  }
  memcpy(backing->Data(), mem->data, mem->length);
#else
  auto backing = ArrayBuffer::NewBackingStore(
      mem->data,
      mem->length,
      [](void*, size_t, void* data) {
        BIOPointer free_me(static_cast<BIO*>(data));
      },
      bio->release());
#endif  // V8_ENABLE_SANDBOX
  auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
  Local<Value> ret;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
  return ret;
}

MaybeLocal<Value> GetDer(Environment* env, const X509View& view) {
  Local<Value> ret;
  auto bio = view.toDER();
  if (!bio) [[unlikely]]
    return Undefined(env->isolate());
  if (!ToBuffer(env, &bio).ToLocal(&ret)) {
    return {};
  }
  return ret;
}

MaybeLocal<Value> GetSubjectAltNameString(Environment* env,
                                          const X509View& view) {
  Local<Value> ret;
  auto bio = view.getSubjectAltName();
  if (!bio) [[unlikely]]
    return Undefined(env->isolate());
  if (!ToV8Value(env->context(), bio).ToLocal(&ret)) return {};
  return ret;
}

MaybeLocal<Value> GetInfoAccessString(Environment* env, const X509View& view) {
  Local<Value> ret;
  auto bio = view.getInfoAccess();
  if (!bio) [[unlikely]]
    return Undefined(env->isolate());
  if (!ToV8Value(env->context(), bio).ToLocal(&ret)) {
    return {};
  }
  return ret;
}

MaybeLocal<Value> GetValidFrom(Environment* env, const X509View& view) {
  Local<Value> ret;
  auto bio = view.getValidFrom();
  if (!bio) [[unlikely]]
    return Undefined(env->isolate());
  if (!ToV8Value(env->context(), bio).ToLocal(&ret)) {
    return {};
  }
  return ret;
}

MaybeLocal<Value> GetValidTo(Environment* env, const X509View& view) {
  Local<Value> ret;
  auto bio = view.getValidTo();
  if (!bio) [[unlikely]]
    return Undefined(env->isolate());
  if (!ToV8Value(env->context(), bio).ToLocal(&ret)) {
    return {};
  }
  return ret;
}

MaybeLocal<Value> GetValidFromDate(Environment* env, const X509View& view) {
  int64_t validFromTime = view.getValidFromTime();
  return Date::New(env->context(), validFromTime * 1000.);
}

MaybeLocal<Value> GetValidToDate(Environment* env, const X509View& view) {
  int64_t validToTime = view.getValidToTime();
  return Date::New(env->context(), validToTime * 1000.);
}

MaybeLocal<Value> GetSignatureAlgorithm(Environment* env,
                                        const X509View& view) {
  auto algo = view.getSignatureAlgorithm();
  if (!algo.has_value()) [[unlikely]]
    return Undefined(env->isolate());
  Local<Value> ret;
  if (!ToV8Value(env, algo.value()).ToLocal(&ret)) {
    return {};
  }
  return ret;
}

MaybeLocal<Value> GetSignatureAlgorithmOID(Environment* env,
                                           const X509View& view) {
  auto oid = view.getSignatureAlgorithmOID();
  if (!oid.has_value()) [[unlikely]]
    return Undefined(env->isolate());
  Local<Value> ret;
  if (!ToV8Value(env, oid.value()).ToLocal(&ret)) {
    return {};
  }
  return ret;
}

MaybeLocal<Value> GetSerialNumber(Environment* env, const X509View& view) {
  if (auto serial = view.getSerialNumber()) {
    return ToV8Value(
        env, ToUpper(std::string_view(static_cast<char*>(serial.get()))));
  }
  return Undefined(env->isolate());
}

MaybeLocal<Value> GetKeyUsage(Environment* env, const X509View& cert) {
  LocalVector<Value> vec(env->isolate());
  bool res = cert.enumUsages([&](std::string_view view) {
    vec.push_back(OneByteString(env->isolate(), view));
  });
  if (!res) return Undefined(env->isolate());
  return Array::New(env->isolate(), vec.data(), vec.size());
}

void Pem(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (ToV8Value(env->context(), cert->view().toPEM()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void Der(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetDer(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void Subject(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (ToV8Value(env->context(), cert->view().getSubject()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void SubjectAltName(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetSubjectAltNameString(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void Issuer(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (ToV8Value(env->context(), cert->view().getIssuer()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void InfoAccess(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetInfoAccessString(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void ValidFrom(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetValidFrom(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void ValidTo(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetValidTo(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void ValidFromDate(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetValidFromDate(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void ValidToDate(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetValidToDate(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void SignatureAlgorithm(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetSignatureAlgorithm(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void SignatureAlgorithmOID(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetSignatureAlgorithmOID(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void SerialNumber(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetSerialNumber(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void PublicKey(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());

  // TODO(tniessen): consider checking X509_get_pubkey() when the
  // X509Certificate object is being created.
  auto result = cert->view().getPublicKey();
  if (!result.value) [[unlikely]] {
    ThrowCryptoError(env, result.error.value_or(0));
    return;
  }
  auto key_data =
      KeyObjectData::CreateAsymmetric(kKeyTypePublic, std::move(result.value));

  Local<Value> ret;
  if (key_data && KeyObjectHandle::Create(env, key_data).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void KeyUsage(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  Local<Value> ret;
  if (GetKeyUsage(env, cert->view()).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void CheckCA(const FunctionCallbackInfo<Value>& args) {
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  args.GetReturnValue().Set(cert->view().isCA());
}

void CheckIssued(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  CHECK(args[0]->IsObject());
  CHECK(X509Certificate::HasInstance(env, args[0].As<Object>()));
  X509Certificate* issuer;
  ASSIGN_OR_RETURN_UNWRAP(&issuer, args[0]);
  args.GetReturnValue().Set(cert->view().isIssuedBy(issuer->view()));
}

void CheckPrivateKey(const FunctionCallbackInfo<Value>& args) {
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  CHECK(args[0]->IsObject());
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args[0]);
  CHECK_EQ(key->Data().GetKeyType(), kKeyTypePrivate);
  args.GetReturnValue().Set(
      cert->view().checkPrivateKey(key->Data().GetAsymmetricKey()));
}

void CheckPublicKey(const FunctionCallbackInfo<Value>& args) {
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());

  CHECK(args[0]->IsObject());
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args[0]);
  // A Public Key can be derived from a private key, so we allow both.
  CHECK_NE(key->Data().GetKeyType(), kKeyTypeSecret);

  args.GetReturnValue().Set(
      cert->view().checkPublicKey(key->Data().GetAsymmetricKey()));
}

template <typename F>
void CheckX509Subject(const FunctionCallbackInfo<Value>& args, F check) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());

  CHECK(args[0]->IsString());  // subject
  CHECK(args[1]->IsUint32());  // flags

  Utf8Value subject(env->isolate(), args[0]);
  uint32_t flags = args[1].As<Uint32>()->Value();

  DataPointer matched_subject;
  X509View view = cert->view();
  auto result = check(view, subject.ToStringView(), flags, &matched_subject);
  switch (result) {
    case X509View::CheckMatch::MATCH: {
      Local<Value> ret = args[0];
      if (matched_subject) {
        ret = OneByteString(env->isolate(),
                            matched_subject.get<const char>(),
                            matched_subject.size());
      }
      return args.GetReturnValue().Set(ret);
    }
    case X509View::CheckMatch::NO_MATCH:
      break;  // No return value is set.
    case X509View::CheckMatch::INVALID_NAME:
      return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid name");
    default:
      return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
  }
}

void CheckHost(const FunctionCallbackInfo<Value>& args) {
  CheckX509Subject(
      args,
      [](X509View& cert,
         std::string_view subject,
         uint32_t flags,
         DataPointer* match) { return cert.checkHost(subject, flags, match); });
}

void CheckEmail(const FunctionCallbackInfo<Value>& args) {
  CheckX509Subject(
      args,
      [](X509View& cert,
         std::string_view subject,
         uint32_t flags,
         DataPointer*) { return cert.checkEmail(subject, flags); });
}

void CheckIP(const FunctionCallbackInfo<Value>& args) {
  CheckX509Subject(args,
                   [](X509View& cert,
                      std::string_view subject,
                      uint32_t flags,
                      DataPointer*) { return cert.checkIp(subject, flags); });
}

void GetIssuerCert(const FunctionCallbackInfo<Value>& args) {
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  auto issuer = cert->getIssuerCert();
  if (issuer) args.GetReturnValue().Set(issuer->object());
}

void Parse(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args[0]->IsArrayBufferView());
  ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());
  Local<Object> cert;

  auto result = X509Pointer::Parse(ncrypto::Buffer<const unsigned char>{
      .data = buf.data(),
      .len = buf.length(),
  });

  if (!result.value) [[unlikely]] {
    return ThrowCryptoError(env, result.error.value_or(0));
  }

  if (X509Certificate::New(env, std::move(result.value)).ToLocal(&cert)) {
    args.GetReturnValue().Set(cert);
  }
}

void ToLegacy(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
  ClearErrorOnReturn clear_error_on_return;
  Local<Value> ret;
  if (cert->toObject(env).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

template <typename T>
bool Set(Environment* env,
         Local<Object> target,
         Local<Value> name,
         MaybeLocal<T> maybe_value) {
  Local<Value> value;
  if (!maybe_value.ToLocal(&value)) [[unlikely]]
    return false;

  // Undefined is ignored, but still considered successful
  if (value->IsUndefined()) return true;

  return !target->Set(env->context(), name, value).IsNothing();
}

template <typename T>
bool Set(Environment* env,
         Local<Object> target,
         uint32_t index,
         MaybeLocal<T> maybe_value) {
  Local<Value> value;
  if (!maybe_value.ToLocal(&value)) [[unlikely]]
    return false;

  // Undefined is ignored, but still considered successful
  if (value->IsUndefined()) return true;

  return !target->Set(env->context(), index, value).IsNothing();
}

// Convert an X509_NAME* into a JavaScript object.
// Each entry of the name is converted into a property of the object.
// The property value may be a single string or an array of strings.
static MaybeLocal<Value> GetX509NameObject(Environment* env,
                                           const X509Name& name) {
  if (!name) return {};

  Local<Value> v8_name;
  Local<Value> v8_value;
  // Note the the resulting object uses a null prototype.
  Local<Object> result =
      Object::New(env->isolate(), Null(env->isolate()), nullptr, nullptr, 0);
  if (result.IsEmpty()) return {};

  for (auto i : name) {
    if (!ToV8Value(env, i.first).ToLocal(&v8_name) ||
        !ToV8Value(env, i.second).ToLocal(&v8_value)) {
      return {};
    }

    // For backward compatibility, we only create arrays if multiple values
    // exist for the same key. That is not great but there is not much we can
    // change here without breaking things. Note that this creates nested data
    // structures, yet still does not allow representing Distinguished Names
    // accurately.
    bool multiple;
    if (!result->Has(env->context(), v8_name).To(&multiple)) {
      return {};
    }

    if (multiple) {
      Local<Value> accum;
      if (!result->Get(env->context(), v8_name).ToLocal(&accum)) {
        return {};
      }
      if (!accum->IsArray()) {
        Local<Value> items[] = {
            accum,
            v8_value,
        };
        accum = Array::New(env->isolate(), items, arraysize(items));
        if (!Set<Value>(env, result, v8_name, accum)) {
          return {};
        }
      } else {
        Local<Array> array = accum.As<Array>();
        if (!Set<Value>(env, array, array->Length(), v8_value)) {
          return {};
        }
      }
      continue;
    }

    if (!Set<Value>(env, result, v8_name, v8_value)) {
      return {};
    }
  }

  return result;
}

MaybeLocal<Object> GetPubKey(Environment* env, const ncrypto::Rsa& rsa) {
  auto bio = rsa.derPublicKey();
  Local<Value> ret;
  if (!ToBuffer(env, &bio).ToLocal(&ret)) return {};
  return ret.As<Object>();
}

MaybeLocal<Value> GetModulusString(Environment* env, const BIGNUM* n) {
  auto bio = BIOPointer::New(n);
  if (!bio) [[unlikely]]
    return {};
  return ToV8Value(env->context(), bio);
}

MaybeLocal<Value> GetExponentString(Environment* env, const BIGNUM* e) {
  if (e == nullptr) return Null(env->isolate());
  auto bio = BIOPointer::NewMem();
  if (!bio) [[unlikely]]
    return {};
  BIO_puts(bio.get(), "0x");
  BN_print(bio.get(), e);
  return ToV8Value(env->context(), bio);
}

MaybeLocal<Value> GetECPubKey(Environment* env, const ncrypto::Ec& ec) {
  const auto group = ec.getGroup();
  const auto pubkey = ec.getPublicKey();
  if (pubkey == nullptr) [[unlikely]]
    return Undefined(env->isolate());

  return ECPointToBuffer(env, group, pubkey, ec.getPointConversionForm())
      .FromMaybe(Local<Object>());
}

MaybeLocal<Value> GetECGroupBits(Environment* env, const EC_GROUP* group) {
  if (group == nullptr) [[unlikely]]
    return Undefined(env->isolate());

  int bits = EC_GROUP_order_bits(group);
  if (bits <= 0) return Undefined(env->isolate());

  return Integer::New(env->isolate(), bits);
}

template <const char* (*nid2string)(int nid)>
MaybeLocal<Value> GetCurveName(Environment* env, const int nid) {
  std::string_view name = nid2string(nid);
  return name.size() ? MaybeLocal<Value>(OneByteString(env->isolate(), name))
                     : MaybeLocal<Value>(Undefined(env->isolate()));
}

MaybeLocal<Object> X509ToObject(Environment* env, const X509View& cert) {
  EscapableHandleScope scope(env->isolate());

  auto tmpl = env->x509_dictionary_template();
  if (tmpl.IsEmpty()) {
    static constexpr std::string_view names[] = {
        "subject",
        "issuer",
        "subjectaltname",
        "infoAccess",
        "ca",
        "modulus",
        "exponent",
        "pubkey",
        "bits",
        "valid_from",
        "valid_to",
        "fingerprint",
        "fingerprint256",
        "fingerprint512",
        "ext_key_usage",
        "serialNumber",
        "raw",
        "asn1Curve",
        "nistCurve",
    };
    tmpl = DictionaryTemplate::New(env->isolate(), names);
    env->set_x509_dictionary_template(tmpl);
  }

  MaybeLocal<Value> values[] = {
      GetX509NameObject(env, cert.getSubjectName()),
      GetX509NameObject(env, cert.getIssuerName()),
      GetSubjectAltNameString(env, cert),
      GetInfoAccessString(env, cert),
      Boolean::New(env->isolate(), cert.isCA()),
      Undefined(env->isolate()),  // modulus
      Undefined(env->isolate()),  // exponent
      Undefined(env->isolate()),  // pubkey
      Undefined(env->isolate()),  // bits
      GetValidFrom(env, cert),
      GetValidTo(env, cert),
      GetFingerprintDigest(env, Digest::SHA1, cert),
      GetFingerprintDigest(env, Digest::SHA256, cert),
      GetFingerprintDigest(env, Digest::SHA512, cert),
      GetKeyUsage(env, cert),
      GetSerialNumber(env, cert),
      GetDer(env, cert),
      Undefined(env->isolate()),  // asn1curve
      Undefined(env->isolate()),  // nistcurve
  };

  cert.ifRsa([&](const ncrypto::Rsa& rsa) {
    auto pub_key = rsa.getPublicKey();
    values[5] = GetModulusString(env, pub_key.n);   // modulus
    values[6] = GetExponentString(env, pub_key.e);  // exponent
    values[7] = GetPubKey(env, rsa);                // pubkey
    values[8] = Integer::New(env->isolate(),
                             BignumPointer::GetBitCount(pub_key.n));  // bits
    // TODO(@jasnell): The true response is a left-over from the original
    // non DictionaryTemplate-based implementation. It can be removed later.
    return true;
  });

  cert.ifEc([&](const ncrypto::Ec& ec) {
    const auto group = ec.getGroup();
    values[7] = GetECPubKey(env, ec);         // pubkey
    values[8] = GetECGroupBits(env, group);   // bits
    const int nid = ec.getCurve();
    if (nid != 0) {
      // Curve is well-known, get its OID and NIST nick-name (if it has
      // one).
      values[17] = GetCurveName<OBJ_nid2sn>(env, nid);         // asn1curve
      values[18] = GetCurveName<EC_curve_nid2nist>(env, nid);  // nistcurve
    }
    // Unnamed curves can be described by their mathematical properties,
    // but aren't used much (at all?) with X.509/TLS. Support later if
    // needed.
    return true;
  });

  return scope.EscapeMaybe(NewDictionaryInstance(env->context(), tmpl, values));
}
}  // namespace

Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
    Environment* env) {
  Local<FunctionTemplate> tmpl = env->x509_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, nullptr);
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        X509Certificate::kInternalFieldCount);
    tmpl->SetClassName(
        FIXED_ONE_BYTE_STRING(env->isolate(), "X509Certificate"));
    SetProtoMethodNoSideEffect(isolate, tmpl, "subject", Subject);
    SetProtoMethodNoSideEffect(isolate, tmpl, "subjectAltName", SubjectAltName);
    SetProtoMethodNoSideEffect(isolate, tmpl, "infoAccess", InfoAccess);
    SetProtoMethodNoSideEffect(isolate, tmpl, "issuer", Issuer);
    SetProtoMethodNoSideEffect(isolate, tmpl, "validTo", ValidTo);
    SetProtoMethodNoSideEffect(isolate, tmpl, "validFrom", ValidFrom);
    SetProtoMethodNoSideEffect(isolate, tmpl, "validToDate", ValidToDate);
    SetProtoMethodNoSideEffect(isolate, tmpl, "validFromDate", ValidFromDate);
    SetProtoMethodNoSideEffect(
        isolate, tmpl, "signatureAlgorithm", SignatureAlgorithm);
    SetProtoMethodNoSideEffect(
        isolate, tmpl, "signatureAlgorithmOid", SignatureAlgorithmOID);
    SetProtoMethodNoSideEffect(
        isolate, tmpl, "fingerprint", Fingerprint<Digest::SHA1>);
    SetProtoMethodNoSideEffect(
        isolate, tmpl, "fingerprint256", Fingerprint<Digest::SHA256>);
    SetProtoMethodNoSideEffect(
        isolate, tmpl, "fingerprint512", Fingerprint<Digest::SHA512>);
    SetProtoMethodNoSideEffect(isolate, tmpl, "keyUsage", KeyUsage);
    SetProtoMethodNoSideEffect(isolate, tmpl, "serialNumber", SerialNumber);
    SetProtoMethodNoSideEffect(isolate, tmpl, "pem", Pem);
    SetProtoMethodNoSideEffect(isolate, tmpl, "raw", Der);
    SetProtoMethodNoSideEffect(isolate, tmpl, "publicKey", PublicKey);
    SetProtoMethodNoSideEffect(isolate, tmpl, "checkCA", CheckCA);
    SetProtoMethodNoSideEffect(isolate, tmpl, "checkHost", CheckHost);
    SetProtoMethodNoSideEffect(isolate, tmpl, "checkEmail", CheckEmail);
    SetProtoMethodNoSideEffect(isolate, tmpl, "checkIP", CheckIP);
    SetProtoMethodNoSideEffect(isolate, tmpl, "checkIssued", CheckIssued);
    SetProtoMethodNoSideEffect(
        isolate, tmpl, "checkPrivateKey", CheckPrivateKey);
    SetProtoMethodNoSideEffect(isolate, tmpl, "verify", CheckPublicKey);
    SetProtoMethodNoSideEffect(isolate, tmpl, "toLegacy", ToLegacy);
    SetProtoMethodNoSideEffect(isolate, tmpl, "getIssuerCert", GetIssuerCert);
    env->set_x509_constructor_template(tmpl);
  }
  return tmpl;
}

bool X509Certificate::HasInstance(Environment* env, Local<Object> object) {
  return GetConstructorTemplate(env)->HasInstance(object);
}

MaybeLocal<Object> X509Certificate::New(Environment* env,
                                        X509Pointer cert,
                                        STACK_OF(X509) * issuer_chain) {
  std::shared_ptr<ManagedX509> mcert(new ManagedX509(std::move(cert)));
  return New(env, std::move(mcert), issuer_chain);
}

MaybeLocal<Object> X509Certificate::New(Environment* env,
                                        std::shared_ptr<ManagedX509> cert,
                                        STACK_OF(X509) * issuer_chain) {
  EscapableHandleScope scope(env->isolate());
  Local<Function> ctor;
  if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
    return MaybeLocal<Object>();

  Local<Object> obj;
  if (!ctor->NewInstance(env->context()).ToLocal(&obj))
    return MaybeLocal<Object>();

  Local<Object> issuer_chain_obj;
  if (issuer_chain != nullptr && sk_X509_num(issuer_chain)) {
    X509Pointer cert(X509_dup(sk_X509_value(issuer_chain, 0)));
    sk_X509_delete(issuer_chain, 0);
    auto maybeObj =
        sk_X509_num(issuer_chain)
            ? X509Certificate::New(env, std::move(cert), issuer_chain)
            : X509Certificate::New(env, std::move(cert));
    if (!maybeObj.ToLocal(&issuer_chain_obj)) [[unlikely]] {
      return MaybeLocal<Object>();
    }
  }

  new X509Certificate(env, obj, std::move(cert), issuer_chain_obj);
  return scope.Escape(obj);
}

MaybeLocal<Object> X509Certificate::GetCert(Environment* env,
                                            const SSLPointer& ssl) {
  auto cert = X509View::From(ssl);
  if (!cert) [[unlikely]]
    return {};
  return New(env, cert.clone());
}

MaybeLocal<Object> X509Certificate::GetPeerCert(Environment* env,
                                                const SSLPointer& ssl,
                                                GetPeerCertificateFlag flag) {
  ClearErrorOnReturn clear_error_on_return;

  X509Pointer cert;
  if ((flag & GetPeerCertificateFlag::SERVER) ==
      GetPeerCertificateFlag::SERVER) {
    cert = X509Pointer::PeerFrom(ssl);
  }

  STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get());
  if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0))
    return MaybeLocal<Object>();

  if (!cert) [[unlikely]] {
    cert.reset(sk_X509_value(ssl_certs, 0));
    sk_X509_delete(ssl_certs, 0);
  }

  return sk_X509_num(ssl_certs) ? New(env, std::move(cert), ssl_certs)
                                : New(env, std::move(cert));
}

v8::MaybeLocal<v8::Value> X509Certificate::toObject(Environment* env) {
  return toObject(env, view());
}

v8::MaybeLocal<v8::Value> X509Certificate::toObject(Environment* env,
                                                    const X509View& cert) {
  if (!cert) [[unlikely]]
    return {};
  return X509ToObject(env, cert).FromMaybe(Local<Value>());
}

X509Certificate::X509Certificate(Environment* env,
                                 Local<Object> object,
                                 std::shared_ptr<ManagedX509> cert,
                                 Local<Object> issuer_chain)
    : BaseObject(env, object), cert_(std::move(cert)) {
  MakeWeak();

  if (!issuer_chain.IsEmpty()) {
    issuer_cert_.reset(Unwrap<X509Certificate>(issuer_chain));
  }
}

void X509Certificate::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("cert", cert_);
}

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

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

  return BaseObjectPtr<BaseObject>(
      Unwrap<X509Certificate>(handle.As<Object>()));
}

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

std::unique_ptr<worker::TransferData> X509Certificate::CloneForMessaging()
    const {
  return std::make_unique<X509CertificateTransferData>(cert_);
}

void X509Certificate::Initialize(Environment* env, Local<Object> target) {
  SetMethod(env->context(), target, "parseX509", Parse);

  NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
  NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_NEVER_CHECK_SUBJECT);
  NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_NO_WILDCARDS);
  NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
  NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS);
  NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS);
}

void X509Certificate::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(Parse);
  registry->Register(Subject);
  registry->Register(SubjectAltName);
  registry->Register(InfoAccess);
  registry->Register(Issuer);
  registry->Register(ValidTo);
  registry->Register(ValidFrom);
  registry->Register(ValidToDate);
  registry->Register(ValidFromDate);
  registry->Register(SignatureAlgorithm);
  registry->Register(SignatureAlgorithmOID);
  registry->Register(Fingerprint<Digest::SHA1>);
  registry->Register(Fingerprint<Digest::SHA256>);
  registry->Register(Fingerprint<Digest::SHA512>);
  registry->Register(KeyUsage);
  registry->Register(SerialNumber);
  registry->Register(Pem);
  registry->Register(Der);
  registry->Register(PublicKey);
  registry->Register(CheckCA);
  registry->Register(CheckHost);
  registry->Register(CheckEmail);
  registry->Register(CheckIP);
  registry->Register(CheckIssued);
  registry->Register(CheckPrivateKey);
  registry->Register(CheckPublicKey);
  registry->Register(ToLegacy);
  registry->Register(GetIssuerCert);
}
}  // namespace crypto
}  // namespace node
