current commit
261104.8 · crypto: add X509Certificate inspection getters
#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 "uv.h"
#include "v8.h"

#include <array>
#include <limits>
#include <numeric>
#include <string>
#include <string_view>
#include <vector>

namespace node {

using ncrypto::BignumPointer;
using ncrypto::BIOPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::DataPointer;
using ncrypto::Digest;
using ncrypto::MarkPopErrorOnReturn;
using ncrypto::SSLPointer;
using ncrypto::X509Name;
using ncrypto::X509NameInspection;
using ncrypto::X509Pointer;
using ncrypto::X509View;
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
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 {

namespace {

std::optional<X509CertificateSnapshot::DER> EncodeCertificate(
    const X509View& cert) {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  if (!cert) return std::nullopt;

  const int size = i2d_X509(cert.get(), nullptr);
  if (size <= 0) return std::nullopt;

  X509CertificateSnapshot::DER der(static_cast<size_t>(size));
  unsigned char* cursor = der.data();
  if (i2d_X509(cert.get(), &cursor) != size ||
      cursor != der.data() + der.size()) {
    return std::nullopt;
  }
  return der;
}

X509Pointer ParseCertificate(const X509CertificateSnapshot::DER& certificate) {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  constexpr size_t kMaximumCertificateSize = static_cast<size_t>(
      std::numeric_limits<long>::max());  // NOLINT(runtime/int)
  if (certificate.empty() || certificate.size() > kMaximumCertificateSize) {
    return {};
  }

  const unsigned char* cursor = certificate.data();
  const unsigned char* const end = cursor + certificate.size();
  X509Pointer cert(
      d2i_X509(nullptr,
               &cursor,
               static_cast<long>(certificate.size())));  // NOLINT(runtime/int)
  if (!cert || cursor != end) return {};
  return cert;
}

}  // namespace

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

std::shared_ptr<ManagedX509> ManagedX509::Adopt(X509Pointer cert) {
  if (!cert) return {};
  return std::shared_ptr<ManagedX509>(new ManagedX509(std::move(cert)));
}

X509CertificateSnapshot::X509CertificateSnapshot(std::vector<DER> certificates)
    : certificates_(std::move(certificates)) {}

std::shared_ptr<const X509CertificateSnapshot> X509CertificateSnapshot::Create(
    X509View leaf,
    const STACK_OF(X509) * issuer_chain,
    OPENSSL_SIZE_T issuers_start) {
  auto leaf_der = EncodeCertificate(leaf);
  if (!leaf_der.has_value()) return {};

  const OPENSSL_SIZE_T count =
      issuer_chain == nullptr ? 0 : sk_X509_num(issuer_chain);
  std::vector<DER> certificates;
  certificates.reserve(static_cast<size_t>(count) + 1);
  certificates.push_back(std::move(leaf_der.value()));
  for (OPENSSL_SIZE_T i = issuers_start; i < count; i++) {
    const X509View candidate(sk_X509_value(issuer_chain, i));
    auto candidate_der = EncodeCertificate(candidate);
    if (!candidate_der.has_value()) return {};
    certificates.push_back(std::move(candidate_der.value()));
  }

  return std::shared_ptr<const X509CertificateSnapshot>(
      new X509CertificateSnapshot(std::move(certificates)));
}

std::shared_ptr<const X509CertificateSnapshot> X509CertificateSnapshot::Create(
    std::vector<DER> certificates) {
  if (certificates.empty()) return {};

  for (auto& certificate : certificates) {
    auto cert = ParseCertificate(certificate);
    if (!cert) return {};

    auto der = EncodeCertificate(cert.view());
    if (!der.has_value()) return {};
    certificate = std::move(der.value());
  }

  return std::shared_ptr<const X509CertificateSnapshot>(
      new X509CertificateSnapshot(std::move(certificates)));
}

X509Pointer X509CertificateSnapshot::Parse(size_t index) const {
  if (index >= certificates_.size()) return {};
  return ParseCertificate(certificates_[index]);
}

size_t X509CertificateSnapshot::size_bytes() const {
  return std::accumulate(certificates_.begin(),
                         certificates_.end(),
                         size_t{0},
                         [](size_t total, const DER& certificate) {
                           return total + certificate.size();
                         });
}

void X509CertificateSnapshot::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("certificates", size_bytes());
}

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

namespace {
enum class MalformedSubjectAltNameMode {
  kThrow,
  kReturnLegacySentinel,
};

constexpr std::string_view kMalformedSubjectAltName = "DNS:";

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(Isolate::GetCurrent(),
                           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(Isolate::GetCurrent(),
                           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 {};
  auto backing = AdoptIntoBackingStore(
      env->isolate(),
      mem->data,
      mem->length,
      [](void*, size_t, void* data) {
        BIOPointer free_me(static_cast<BIO*>(data));
      },
      bio->release());
  if (!backing) {
    THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
    return MaybeLocal<Value>();
  }
  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,
    MalformedSubjectAltNameMode malformed_name_mode) {
  Local<Value> ret;
  bool has_extension = false;
  auto bio = view.getSubjectAltName(&has_extension);
  if (!bio) [[unlikely]] {
    // An absent SAN maps to undefined. A malformed, present SAN throws for
    // direct X509Certificate inspection, while the legacy TLS object uses a
    // deliberately nonmatching value to prevent commonName fallback.
    if (!has_extension) return Undefined(env->isolate());
    if (malformed_name_mode ==
        MalformedSubjectAltNameMode::kReturnLegacySentinel) {
      // The legacy object is used by TLS hostname verification. Return an
      // empty DNS name so verification cannot fall back to the common name,
      // while keeping getPeerCertificate() usable for malformed certificates.
      return OneByteString(env->isolate(), kMalformedSubjectAltName);
    }
    THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "Failed to format subject alternative name");
    return {};
  }
  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) {
  const auto valid_from_time = view.getValidFromTime();
  const double milliseconds = valid_from_time.has_value()
                                  ? valid_from_time.value() * 1000.
                                  : std::numeric_limits<double>::quiet_NaN();
  return Date::New(env->context(), milliseconds);
}

MaybeLocal<Value> GetValidToDate(Environment* env, const X509View& view) {
  const auto valid_to_time = view.getValidToTime();
  const double milliseconds = valid_to_time.has_value()
                                  ? valid_to_time.value() * 1000.
                                  : std::numeric_limits<double>::quiet_NaN();
  return Date::New(env->context(), milliseconds);
}

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

bool SetValue(Local<Context> context,
              Local<Object> object,
              Local<String> name,
              Local<Value> value) {
  return object->CreateDataProperty(context, name, value).FromMaybe(false);
}

bool SetArrayValue(Local<Context> context,
                   Local<Array> array,
                   uint32_t index,
                   Local<Value> value) {
  return array->CreateDataProperty(context, index, value).FromMaybe(false);
}

MaybeLocal<Value> CopyBuffer(Environment* env,
                             ncrypto::Buffer<const unsigned char> value) {
  const char* data =
      value.len == 0 ? "" : reinterpret_cast<const char*>(value.data);
  return Buffer::Copy(env, data, value.len);
}

MaybeLocal<Value> CopyBuffer(Environment* env,
                             const std::vector<unsigned char>& value) {
  return CopyBuffer(env, {.data = value.data(), .len = value.size()});
}

std::optional<std::string> OidToText(const ASN1_OBJECT* object) {
  if (object == nullptr) return std::nullopt;
  const int length = OBJ_obj2txt(nullptr, 0, object, 1);
  if (length <= 0 || length == std::numeric_limits<int>::max()) {
    return std::nullopt;
  }
  std::string output(static_cast<size_t>(length) + 1, '\0');
  if (OBJ_obj2txt(output.data(), length + 1, object, 1) != length) {
    return std::nullopt;
  }
  output.resize(static_cast<size_t>(length));
  return output;
}

struct SequenceSpan final {
  ncrypto::Buffer<const unsigned char> encoded;
  ncrypto::Buffer<const unsigned char> content;
};

bool ReadSequence(ncrypto::Buffer<const unsigned char> input,
                  SequenceSpan* out) {
  if (out == nullptr || input.data == nullptr || input.len == 0 ||
      input.len > static_cast<size_t>(LONG_MAX)) {
    return false;
  }

  const unsigned char* const start = input.data;
  const unsigned char* content = start;
  long content_length = 0;  // NOLINT(runtime/int)
  int tag = 0;
  int tag_class = 0;
  const int flags =
      ASN1_get_object(&content,
                      &content_length,
                      &tag,
                      &tag_class,
                      static_cast<long>(input.len));  // NOLINT(runtime/int)
  constexpr int kIndefiniteLength = 0x01;
  constexpr int kDecodeError = 0x80;
  if ((flags & (kIndefiniteLength | kDecodeError)) != 0 ||
      (flags & V_ASN1_CONSTRUCTED) == 0 || tag != V_ASN1_SEQUENCE ||
      tag_class != V_ASN1_UNIVERSAL || content_length < 0) {
    return false;
  }

  const size_t header_length = static_cast<size_t>(content - start);
  const size_t length = static_cast<size_t>(content_length);
  if (header_length > input.len || length > input.len - header_length) {
    return false;
  }
  *out = {
      .encoded = {.data = start, .len = header_length + length},
      .content = {.data = content, .len = length},
  };
  return true;
}

std::optional<ncrypto::Buffer<const unsigned char>> GetExactTbs(
    const X509CertificateSnapshot::DER& certificate) {
  SequenceSpan outer;
  const ncrypto::Buffer<const unsigned char> encoded{
      .data = certificate.data(),
      .len = certificate.size(),
  };
  if (!ReadSequence(encoded, &outer) || outer.encoded.len != encoded.len) {
    return std::nullopt;
  }

  SequenceSpan tbs;
  if (!ReadSequence(outer.content, &tbs)) return std::nullopt;
  return tbs.encoded;
}

bool GetNameDer(const X509_NAME* name,
                ncrypto::Buffer<const unsigned char>* encoded) {
  if (name == nullptr || encoded == nullptr) return false;
  const unsigned char* data = nullptr;
  size_t length = 0;
  if (X509_NAME_get0_der(name, &data, &length) != 1 ||
      (data == nullptr && length != 0)) {
    return false;
  }
  *encoded = {.data = data, .len = length};
  return true;
}

MaybeLocal<Value> CreateName(Environment* env,
                             const X509NameInspection& native,
                             const X509_NAME* name) {
  Local<Context> context = env->context();
  size_t rdn_count = 0;
  for (const auto& attribute : native) {
    if (attribute.set < 0) return {};
    const size_t set = static_cast<size_t>(attribute.set);
    if (set == rdn_count) {
      rdn_count++;
    } else if (rdn_count == 0 || set != rdn_count - 1) {
      return {};
    }
  }

  if (rdn_count > static_cast<size_t>(INT_MAX)) return {};
  Local<Array> rdns = Array::New(env->isolate(), static_cast<int>(rdn_count));
  LocalVector<Array> groups(env->isolate());
  groups.reserve(rdn_count);
  std::vector<uint32_t> group_lengths(rdn_count, 0);
  for (size_t i = 0; i < rdn_count; i++) {
    Local<Array> group = Array::New(env->isolate());
    if (!SetArrayValue(context, rdns, i, group)) return {};
    groups.push_back(group);
  }

  for (const auto& attribute : native) {
    Local<Object> output = Object::New(env->isolate());
    if (!SetValue(context,
                  output,
                  env->oid_string(),
                  OneByteString(env->isolate(), attribute.oid))) {
      return {};
    }
    if (attribute.value.has_value()) {
      Local<Value> value;
      if (!ToV8Value(env, attribute.value.value()).ToLocal(&value) ||
          !SetValue(context, output, env->value_string(), value)) {
        return {};
      }
    }
    const size_t rdn_index = static_cast<size_t>(attribute.set);
    if (!SetArrayValue(
            context, groups[rdn_index], group_lengths[rdn_index]++, output)) {
      return {};
    }
  }

  ncrypto::Buffer<const unsigned char> encoded;
  Local<Value> raw;
  Local<Object> output = Object::New(env->isolate());
  if (!GetNameDer(name, &encoded) || !CopyBuffer(env, encoded).ToLocal(&raw) ||
      !SetValue(context, output, env->raw_string(), raw) ||
      !SetValue(context, output, env->rdns_string(), rdns)) {
    return {};
  }
  return output;
}

MaybeLocal<Value> CreateRawExtensions(
    Environment* env,
    const std::vector<ncrypto::X509RawExtension>& extensions) {
  Local<Context> context = env->context();
  if (extensions.size() > static_cast<size_t>(INT_MAX)) return {};
  Local<Array> output =
      Array::New(env->isolate(), static_cast<int>(extensions.size()));
  for (size_t i = 0; i < extensions.size(); i++) {
    const auto& extension = extensions[i];
    Local<Object> value = Object::New(env->isolate());
    Local<Value> bytes;
    if (!CopyBuffer(env, extension.value).ToLocal(&bytes) ||
        !SetValue(context,
                  value,
                  env->oid_string(),
                  OneByteString(env->isolate(), extension.oid)) ||
        !SetValue(context,
                  value,
                  env->critical_string(),
                  Boolean::New(env->isolate(), extension.critical)) ||
        !SetValue(context, value, env->value_string(), bytes) ||
        !SetArrayValue(context, output, i, value)) {
      return {};
    }
  }
  return output;
}

MaybeLocal<Value> CreateAlgorithmIdentifier(Environment* env,
                                            const X509_ALGOR* native) {
  if (native == nullptr) return {};
  const ASN1_OBJECT* native_oid = nullptr;
  int parameter_type = V_ASN1_UNDEF;
  const void* parameter_value = nullptr;
  X509_ALGOR_get0(&native_oid, &parameter_type, &parameter_value, native);
  auto oid = OidToText(native_oid);
  if (!oid.has_value()) return {};

  Local<Context> context = env->context();
  Local<Object> output = Object::New(env->isolate());
  if (!SetValue(context,
                output,
                env->oid_string(),
                OneByteString(env->isolate(), oid.value()))) {
    return {};
  }
  if (parameter_type != V_ASN1_UNDEF) {
    ncrypto::DeleteFnPtr<ASN1_TYPE, ASN1_TYPE_free> native_parameters(
        ASN1_TYPE_new());
    if (!native_parameters ||
        ASN1_TYPE_set1(
            native_parameters.get(), parameter_type, parameter_value) != 1) {
      return {};
    }
    const int length = i2d_ASN1_TYPE(native_parameters.get(), nullptr);
    if (length <= 0) return {};
    std::vector<unsigned char> encoded(static_cast<size_t>(length));
    unsigned char* cursor = encoded.data();
    if (i2d_ASN1_TYPE(native_parameters.get(), &cursor) != length ||
        cursor != encoded.data() + encoded.size()) {
      return {};
    }

    Local<Value> parameters;
    if (!CopyBuffer(env, encoded).ToLocal(&parameters) ||
        !SetValue(context, output, env->parameters_string(), parameters)) {
      return {};
    }
  }
  return output;
}

MaybeLocal<Value> CreateSignature(Environment* env,
                                  const X509View& certificate) {
  const ASN1_BIT_STRING* native_signature = nullptr;
  const X509_ALGOR* native_algorithm = nullptr;
  X509_get0_signature(&native_signature, &native_algorithm, certificate.get());
  if (native_signature == nullptr || native_algorithm == nullptr ||
      ASN1_STRING_type(native_signature) != V_ASN1_BIT_STRING) {
    return {};
  }

  const int native_length = ASN1_STRING_length(native_signature);
  const unsigned char* native_data = ASN1_STRING_get0_data(native_signature);
  if (native_length < 0 || (native_length > 0 && native_data == nullptr)) {
    return {};
  }
#if NCRYPTO_HAS_ASN1_BIT_STRING_GET_LENGTH
  size_t byte_length = 0;
  int unused_bits = 0;
  if (ASN1_BIT_STRING_get_length(
          native_signature, &byte_length, &unused_bits) != 1 ||
      unused_bits != 0 || byte_length != static_cast<size_t>(native_length)) {
    return {};
  }
#elif NCRYPTO_USE_OPENSSL_PROVIDER || NCRYPTO_USE_BORINGSSL
  if ((native_signature->flags & ASN1_STRING_FLAG_BITS_LEFT) != 0 &&
      (native_signature->flags & 0x07) != 0) {
    return {};
  }
#else
#error "Unsupported crypto backend"
#endif

  const ncrypto::Buffer<const unsigned char> signature{
      .data = native_data,
      .len = static_cast<size_t>(native_length),
  };

  Local<Value> algorithm;
  Local<Value> value;
  if (!CreateAlgorithmIdentifier(env, native_algorithm).ToLocal(&algorithm) ||
      !CopyBuffer(env, signature).ToLocal(&value)) {
    return {};
  }

  Local<Context> context = env->context();
  Local<Object> output = Object::New(env->isolate());
  if (!SetValue(context, output, env->algorithm_string(), algorithm) ||
      !SetValue(context, output, env->value_string(), value)) {
    return {};
  }
  return output;
}

void Version(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* certificate;
  ASSIGN_OR_RETURN_UNWRAP(&certificate, args.This());
  const auto version = certificate->view().getVersion();
  if (!version.has_value()) return;
  args.GetReturnValue().Set(
      Integer::New(env->isolate(), static_cast<int32_t>(version.value())));
}

void StructuredName(const FunctionCallbackInfo<Value>& args, bool subject) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* certificate;
  ASSIGN_OR_RETURN_UNWRAP(&certificate, args.This());

  const auto native = subject ? certificate->view().inspectSubjectName()
                              : certificate->view().inspectIssuerName();
  if (!native.has_value()) return;

  MarkPopErrorOnReturn mark_pop_error_on_return;
  const X509_NAME* name = subject ? X509_get_subject_name(certificate->get())
                                  : X509_get_issuer_name(certificate->get());
  Local<Value> output;
  if (!CreateName(env, native.value(), name).ToLocal(&output)) {
    return;
  }
  args.GetReturnValue().Set(output);
}

void SubjectName(const FunctionCallbackInfo<Value>& args) {
  StructuredName(args, true);
}

void IssuerName(const FunctionCallbackInfo<Value>& args) {
  StructuredName(args, false);
}

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

  const auto extensions = certificate->view().inspectExtensions();
  Local<Value> output;
  if (!extensions.has_value() ||
      !CreateRawExtensions(env, extensions.value()).ToLocal(&output)) {
    return;
  }
  args.GetReturnValue().Set(output);
}

void Tbs(const FunctionCallbackInfo<Value>& args) {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* certificate;
  ASSIGN_OR_RETURN_UNWRAP(&certificate, args.This());

  const auto tbs = GetExactTbs(certificate->snapshot_der());
  Local<Value> output;
  if (!tbs.has_value() || !CopyBuffer(env, tbs.value()).ToLocal(&output)) {
    return;
  }
  args.GetReturnValue().Set(output);
}

void Signature(const FunctionCallbackInfo<Value>& args) {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* certificate;
  ASSIGN_OR_RETURN_UNWRAP(&certificate, args.This());

  Local<Value> output;
  if (!CreateSignature(env, certificate->view()).ToLocal(&output)) {
    return;
  }
  args.GetReturnValue().Set(output);
}

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(), MalformedSubjectAltNameMode::kThrow)
          .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*) {
        if (subject.find('\0') != std::string_view::npos ||
            subject.find('%') != std::string_view::npos) {
          return X509View::CheckMatch::INVALID_NAME;
        }

        const std::string input(subject);
        std::array<unsigned char, 16> address{};
        size_t length = 0;
        if (uv_inet_pton(AF_INET, input.c_str(), address.data()) == 0) {
          length = 4;
        } else if (uv_inet_pton(AF_INET6, input.c_str(), address.data()) == 0) {
          length = 16;
        } else {
          return X509View::CheckMatch::INVALID_NAME;
        }
        return cert.checkIp({.data = address.data(), .len = length}, 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 entry : name) {
    if (!entry.has_value()) [[unlikely]] {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                        "Failed to convert X509 name to UTF-8");
      return {};
    }

    const auto& [key, value] = *entry;
    // The legacy object can only represent string values. Preserve valid
    // sibling attributes and omit values that cannot be converted to UTF-8.
    if (!value.has_value()) continue;

    if (!ToV8Value(env, key).ToLocal(&v8_name) ||
        !ToV8Value(env, *value).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());
  MarkPopErrorOnReturn mark_pop_error_on_return;
  auto bio = BIOPointer::NewMem();
  if (!bio) [[unlikely]]
    return {};
  if (!BIOPointer::WriteAll(bio, "0x") || BN_print(bio.get(), e) != 1) {
    return {};
  }
  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) {
  const char* const curve_name = nid2string(nid);
  if (curve_name == nullptr) return Undefined(env->isolate());
  const std::string_view name = curve_name;
  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);
  }

  Local<Value> subject;
  if (!GetX509NameObject(env, cert.getSubjectName()).ToLocal(&subject)) {
    return {};
  }

  Local<Value> issuer;
  if (!GetX509NameObject(env, cert.getIssuerName()).ToLocal(&issuer)) {
    return {};
  }

  MaybeLocal<Value> values[] = {
      subject,
      issuer,
      GetSubjectAltNameString(
          env, cert, MalformedSubjectAltNameMode::kReturnLegacySentinel),
      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
  };

  // X509_get0_pubkey() fails when the linked OpenSSL cannot decode the
  // certificate's public key (e.g. an unsupported curve in FIPS builds).
  // ifRsa()/ifEc() assume a decodable key, so leave the key-specific
  // fields undefined in that case.
  const bool has_public_key = [&] {
    MarkPopErrorOnReturn mark_pop_error_on_return;
    return X509_get0_pubkey(cert.get()) != nullptr;
  }();
  if (!has_public_key) {
    return scope.EscapeMaybe(
        NewDictionaryInstance(env->context(), tmpl, values));
  }

  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, NewFromHandle);
    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, "version", Version);
    SetProtoMethodNoSideEffect(isolate, tmpl, "subjectName", SubjectName);
    SetProtoMethodNoSideEffect(isolate, tmpl, "issuerName", IssuerName);
    SetProtoMethodNoSideEffect(isolate, tmpl, "extensions", Extensions);
    SetProtoMethodNoSideEffect(isolate, tmpl, "tbs", Tbs);
    SetProtoMethodNoSideEffect(isolate, tmpl, "signature", Signature);
    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<Value> value) {
  return GetConstructorTemplate(env)->HasInstance(value);
}

void X509Certificate::NewFromHandle(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  if (args.Length() == 1 && args[0]->IsArrayBufferView()) {
    ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());
    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));
    }

    auto snapshot = X509CertificateSnapshot::Create(result.value.view());
    if (!snapshot) [[unlikely]] {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                        "Failed to snapshot X.509 certificate");
      return;
    }

    auto parsed = snapshot->Parse(0);
    if (!parsed) [[unlikely]] {
      THROW_ERR_CRYPTO_OPERATION_FAILED(
          env, "Failed to parse X.509 certificate snapshot");
      return;
    }
    new X509Certificate(env,
                        args.This(),
                        ManagedX509::Adopt(std::move(parsed)),
                        std::move(snapshot),
                        0);
    return;
  }

  if (args.Length() != 1 || !HasInstance(env, args[0])) {
    THROW_ERR_INVALID_ARG_TYPE(
        env, "value must be an X509CertificateHandle or BufferSource");
    return;
  }

  X509Certificate* handle = Unwrap<X509Certificate>(args[0].As<Object>());
  CHECK_NOT_NULL(handle);
  Local<Object> issuer;
  if (handle->issuer_cert_) issuer = handle->issuer_cert_->object();
  new X509Certificate(env,
                      args.This(),
                      handle->cert_,
                      handle->snapshot_,
                      handle->snapshot_index_,
                      issuer);
}

void X509Certificate::ParseSnapshot(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Local<Context> context = env->context();
  if (args.Length() != 1 || !args[0]->IsArray()) {
    THROW_ERR_INVALID_ARG_TYPE(env,
                               "The \"snapshot\" argument must be an Array");
    return;
  }

  Local<Array> input = args[0].As<Array>();
  std::vector<X509CertificateSnapshot::DER> certificates;
  certificates.reserve(input->Length());
  for (uint32_t i = 0; i < input->Length(); i++) {
    Local<Value> value;
    if (!input->Get(context, i).ToLocal(&value)) return;
    if (!value->IsArrayBufferView()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env, "The \"snapshot\" entries must be ArrayBufferViews");
      return;
    }

    ArrayBufferViewContents<unsigned char> contents(
        value.As<ArrayBufferView>());
    certificates.emplace_back(contents.data(),
                              contents.data() + contents.length());
  }

  auto snapshot = X509CertificateSnapshot::Create(std::move(certificates));
  if (!snapshot) [[unlikely]] {
    THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "Failed to create X.509 certificate snapshot");
    return;
  }

  Local<Object> cert;
  if (New(env, std::move(snapshot)).ToLocal(&cert)) {
    args.GetReturnValue().Set(cert);
  }
}

void X509Certificate::GetSnapshot(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Local<Context> context = env->context();
  if (args.Length() != 1 || !HasInstance(env, args[0])) {
    THROW_ERR_INVALID_ARG_TYPE(env,
                               "The \"certificate\" argument must be an "
                               "X509CertificateHandle");
    return;
  }

  X509Certificate* cert = Unwrap<X509Certificate>(args[0].As<Object>());
  CHECK_NOT_NULL(cert);
  CHECK_LT(cert->snapshot_index_, cert->snapshot_->size());
  const size_t count = cert->snapshot_->size() - cert->snapshot_index_;
  CHECK_LE(count, static_cast<size_t>(std::numeric_limits<int>::max()));
  Local<Array> output = Array::New(env->isolate(), static_cast<int>(count));
  for (size_t i = 0; i < count; i++) {
    const auto& der = cert->snapshot_->at(cert->snapshot_index_ + i);
    Local<Object> buffer;
    if (!Buffer::Copy(
             env, reinterpret_cast<const char*>(der.data()), der.size())
             .ToLocal(&buffer) ||
        !output->CreateDataProperty(context, static_cast<uint32_t>(i), buffer)
             .FromMaybe(false)) {
      return;
    }
  }
  args.GetReturnValue().Set(output);
}

void X509Certificate::CreateX509CertificateClass(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 1);
  CHECK(args[0]->IsFunction());

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

  Local<Value> ret;
  if (!args[0]
           .As<Function>()
           ->Call(env->context(), Undefined(env->isolate()), 1, &ctor)
           .ToLocal(&ret)) {
    return;
  }

  Local<Array> constructors = ret.As<Array>();
  Local<Value> internal_ctor;
  if (!constructors->Get(env->context(), 1).ToLocal(&internal_ctor)) return;
  CHECK(env->crypto_internal_x509_certificate_constructor().IsEmpty());
  env->set_crypto_internal_x509_certificate_constructor(
      internal_ctor.As<Function>());
  args.GetReturnValue().Set(constructors);
}

void X509Certificate::IsX509Certificate(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 1);
  args.GetReturnValue().Set(HasInstance(env, args[0]));
}

MaybeLocal<Object> X509Certificate::New(Environment* env,
                                        X509Pointer cert,
                                        const STACK_OF(X509) * issuer_chain,
                                        OPENSSL_SIZE_T issuers_start) {
  if (!cert) return {};

  auto snapshot =
      X509CertificateSnapshot::Create(cert.view(), issuer_chain, issuers_start);
  if (!snapshot) [[unlikely]] {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                      "Failed to snapshot X.509 certificate");
    return {};
  }

  // Keep the live certificate independent from the backend object that was
  // serialized while creating the immutable snapshot. Some backends populate
  // or invalidate lazy caches while encoding malformed-but-parseable input.
  auto parsed = snapshot->Parse(0);
  if (!parsed) [[unlikely]] {
    THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "Failed to parse X.509 certificate snapshot");
    return {};
  }
  return New(env, ManagedX509::Adopt(std::move(parsed)), std::move(snapshot));
}

MaybeLocal<Object> X509Certificate::New(
    Environment* env,
    std::shared_ptr<ManagedX509> cert,
    std::shared_ptr<const X509CertificateSnapshot> snapshot,
    size_t snapshot_index) {
  CHECK(cert);
  CHECK(snapshot);
  CHECK_LT(snapshot_index, snapshot->size());

  EscapableHandleScope scope(env->isolate());
  Local<Context> context = env->context();
  Local<Object> issuer;

  for (size_t i = snapshot->size(); i > snapshot_index + 1; i--) {
    const size_t index = i - 1;
    auto parsed = snapshot->Parse(index);
    if (!parsed) [[unlikely]] {
      THROW_ERR_CRYPTO_OPERATION_FAILED(
          env, "Failed to parse X.509 certificate snapshot");
      return {};
    }

    Local<Object> issuer_object;
    if (!GetConstructorTemplate(env)
             ->InstanceTemplate()
             ->NewInstance(context)
             .ToLocal(&issuer_object)) {
      return {};
    }
    new X509Certificate(env,
                        issuer_object,
                        ManagedX509::Adopt(std::move(parsed)),
                        snapshot,
                        index,
                        issuer);
    issuer = issuer_object;
  }

  Local<Object> obj;
  if (!GetConstructorTemplate(env)
           ->InstanceTemplate()
           ->NewInstance(context)
           .ToLocal(&obj)) {
    return {};
  }
  new X509Certificate(
      env, obj, std::move(cert), std::move(snapshot), snapshot_index, issuer);
  return scope.Escape(obj);
}

MaybeLocal<Object> X509Certificate::New(
    Environment* env,
    std::shared_ptr<const X509CertificateSnapshot> snapshot,
    size_t snapshot_index) {
  if (!snapshot || snapshot_index >= snapshot->size()) return {};
  auto cert = snapshot->Parse(snapshot_index);
  if (!cert) [[unlikely]] {
    THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "Failed to parse X.509 certificate snapshot");
    return {};
  }
  return New(env,
             ManagedX509::Adopt(std::move(cert)),
             std::move(snapshot),
             snapshot_index);
}

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;

  // The peer chain is owned by the SSL session and must not be modified. Its
  // first entry is the peer certificate on the client but not on the server.
  const STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get());
  OPENSSL_SIZE_T issuers_start = 0;

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

  if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0))
    return MaybeLocal<Object>();

  if (!cert) {
    const X509* leaf = sk_X509_value(ssl_certs, 0);
    if (leaf != nullptr) cert = X509View(leaf).clone();
    issuers_start = 1;
  }

  if (!cert) return {};
  return New(env, std::move(cert), ssl_certs, issuers_start);
}

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,
    std::shared_ptr<const X509CertificateSnapshot> snapshot,
    size_t snapshot_index,
    Local<Object> issuer_chain)
    : BaseObject(env, object),
      cert_(std::move(cert)),
      snapshot_(std::move(snapshot)),
      snapshot_index_(snapshot_index) {
  MakeWeak();

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

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

void X509Certificate::X509CertificateTransferData::MemoryInfo(
    MemoryTracker* tracker) const {
  tracker->TrackField("snapshot", snapshot_);
}

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, snapshot_, snapshot_index_).ToLocal(&handle))
    return {};

  Local<Value> module =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/crypto/x509");
  if (env->builtin_module_require()
          ->Call(context, Null(env->isolate()), 1, &module)
          .IsEmpty()) {
    return {};
  }

  Local<Function> ctor = env->crypto_internal_x509_certificate_constructor();
  CHECK(!ctor.IsEmpty());
  Local<Value> cert;
  if (!ctor->NewInstance(context, 1, &handle).ToLocal(&cert)) return {};

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

BaseObject::TransferMode X509Certificate::GetTransferMode() const {
  Local<Value> transfer_mode =
      object()
          ->GetPrivate(env()->context(), env()->transfer_mode_private_symbol())
          .ToLocalChecked();
  if (transfer_mode->IsUint32() &&
      (transfer_mode.As<Uint32>()->Value() & TransferMode::kCloneable) == 0) {
    return BaseObject::TransferMode::kDisallowCloneAndTransfer;
  }
  return BaseObject::TransferMode::kCloneable;
}

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

void X509Certificate::Initialize(Environment* env, Local<Object> target) {
  SetMethod(env->context(),
            target,
            "createX509CertificateClass",
            CreateX509CertificateClass);
  SetMethodNoSideEffect(
      env->context(), target, "isX509Certificate", IsX509Certificate);
  SetMethod(env->context(), target, "parseX509", Parse);
  SetMethod(env->context(), target, "parseX509Snapshot", ParseSnapshot);
  SetMethodNoSideEffect(
      env->context(), target, "getX509CertificateSnapshot", GetSnapshot);

  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(CreateX509CertificateClass);
  registry->Register(IsX509Certificate);
  registry->Register(NewFromHandle);
  registry->Register(Parse);
  registry->Register(ParseSnapshot);
  registry->Register(GetSnapshot);
  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(Version);
  registry->Register(SubjectName);
  registry->Register(IssuerName);
  registry->Register(Extensions);
  registry->Register(Tbs);
  registry->Register(Signature);
  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