@ 261106.1
current
#include "crypto/crypto_util.h"
#include "async_wrap-inl.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_keys.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node_buffer.h"
#include "node_diagnostics_channel.h"
#include "node_options-inl.h"
#include "node_realm-inl.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "util-inl.h"
#include "v8.h"

#include <atomic>
#include <deque>
#include <mutex>
#include "math.h"

#ifndef OPENSSL_IS_BORINGSSL
#include "openssl/provider.h"
#endif

#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 4)
#include <openssl/indicator.h>
#endif

#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 0)
#include <openssl/core_names.h>
#include <openssl/params.h>
#endif

namespace node {

using ncrypto::BignumPointer;
using ncrypto::BIOPointer;
using ncrypto::CryptoErrorList;
using ncrypto::DataPointer;
using ncrypto::SSLPointer;
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BigInt;
using v8::Context;
using v8::DictionaryTemplate;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::LocalVector;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Nothing;
using v8::Object;
using v8::String;
using v8::TryCatch;
using v8::Uint32;
using v8::Uint8Array;
using v8::Value;

void MemoryRetainerTraits<crypto::ByteSource>::MemoryInfo(
    MemoryTracker* tracker, const crypto::ByteSource& value) {
  // Foreign ByteSources do not own the memory that they reference.
  if (value.allocated_data_ != nullptr) {
    tracker->TrackFieldWithSize("data", value.size_);
  }
}

const char* MemoryRetainerTraits<crypto::ByteSource>::MemoryInfoName(
    const crypto::ByteSource& value) {
  return "ByteSource";
}

size_t MemoryRetainerTraits<crypto::ByteSource>::SelfSize(
    const crypto::ByteSource& value) {
  return sizeof(value);
}

namespace crypto {

CShakeOptions::CShakeOptions(CShakeOptions&& other) noexcept
    : function_name(std::move(other.function_name)),
      customization(std::move(other.customization)),
      flags(other.flags) {}

CShakeOptions& CShakeOptions::operator=(CShakeOptions&& other) noexcept {
  if (&other == this) return *this;
  this->~CShakeOptions();
  return *new (this) CShakeOptions(std::move(other));
}

void CShakeOptions::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("function_name", function_name.size());
  tracker->TrackFieldWithSize("customization", customization.size());
}

bool CShakeOptions::Initialize(ncrypto::EVPMDCtxPointer* ctx,
                               const EVP_MD* digest) const {
  if (!ctx || !*ctx || digest == nullptr) return false;
  if (empty()) return ctx->digestInit(digest);

#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 0)
  const bool is_cshake =
      EVP_MD_is_a(digest, "CSHAKE-128") || EVP_MD_is_a(digest, "CSHAKE-256");
  if (!is_cshake) return false;

  OSSL_PARAM params[3];
  size_t count = 0;
  if (has(kFunctionName)) {
    params[count++] = OSSL_PARAM_construct_utf8_string(
        OSSL_DIGEST_PARAM_FUNCTION_NAME,
        const_cast<char*>(function_name.c_str()),
        function_name.size());
  }
  if (has(kCustomization)) {
    params[count++] = OSSL_PARAM_construct_utf8_string(
        OSSL_DIGEST_PARAM_CUSTOMIZATION,
        const_cast<char*>(customization.c_str()),
        customization.size());
  }
  params[count] = OSSL_PARAM_construct_end();
  return ctx->digestInit(digest, params);
#else
  return false;
#endif
}

namespace {
bool ContainsNullByte(std::string_view value) {
  return value.find('\0') != std::string_view::npos;
}

v8::Maybe<void> GetDigestStringOption(
    Environment* env,
    const v8::FunctionCallbackInfo<v8::Value>& args,
    unsigned int offset,
    CShakeOptions::Flag flag,
    std::string* target,
    CShakeOptions* options) {
  if (args[offset]->IsUndefined()) return v8::JustVoid();
  CHECK(IsAnyBufferSource(args[offset]));
  ArrayBufferOrViewContents<char> value(args[offset]);
  if (!value.CheckSizeInt32()) {
    THROW_ERR_OUT_OF_RANGE(env, "digest option is too big");
    return v8::Nothing<void>();
  }
  target->assign(value.data(), value.size());
  if (ContainsNullByte(*target)) {
    THROW_ERR_INVALID_ARG_VALUE(env,
                                "Digest options must not contain null bytes");
    return v8::Nothing<void>();
  }
  options->flags |= flag;
  return v8::JustVoid();
}
}  // namespace

v8::Maybe<void> GetCShakeOptions(
    const v8::FunctionCallbackInfo<v8::Value>& args,
    unsigned int offset,
    CShakeOptions* options) {
  Environment* env = Environment::GetCurrent(args);
  if (GetDigestStringOption(env,
                            args,
                            offset,
                            CShakeOptions::kFunctionName,
                            &options->function_name,
                            options)
          .IsNothing() ||
      GetDigestStringOption(env,
                            args,
                            offset + 1,
                            CShakeOptions::kCustomization,
                            &options->customization,
                            options)
          .IsNothing()) {
    return v8::Nothing<void>();
  }

  return v8::JustVoid();
}

int PasswordCallback(char* buf, int size, int rwflag, void* u) {
  const ByteSource* passphrase = *static_cast<const ByteSource**>(u);
  if (passphrase != nullptr) {
    size_t buflen = static_cast<size_t>(size);
    size_t len = passphrase->size();
    if (buflen < len)
      return -1;
    memcpy(buf, passphrase->data(), len);
    return len;
  }

  return -1;
}

// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
// for the OpenSSL CLI, but works poorly for Node.js because it involves
// synchronous interaction with the controlling terminal, something we never
// want, and use this function to avoid it.
int NoPasswordCallback(char* buf, int size, int rwflag, void* u) {
  return 0;
}

#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 4)
namespace {

constexpr size_t kMaxPendingFipsIndicatorEvents = 256;
constexpr std::string_view kFipsIndicatorChannel = "crypto.fips.indicator";

struct FipsIndicatorEvent {
  std::string operation;
  std::string reason;
  bool blocked;
  uint32_t count = 1;
  uint32_t dropped = 0;
};

Local<DictionaryTemplate> GetFipsIndicatorEventTemplate(Environment* env) {
  auto tmpl = env->fips_indicator_event_template();
  if (tmpl.IsEmpty()) {
    static constexpr std::string_view names[] = {
        "operation",
        "reason",
        "blocked",
        "count",
        "dropped",
    };
    tmpl = DictionaryTemplate::New(env->isolate(), names);
    env->set_fips_indicator_event_template(tmpl);
  }
  return tmpl;
}

class FipsIndicatorState final {
 public:
  static FipsIndicatorState& Get() {
    static FipsIndicatorState state;
    return state;
  }

  void Install() {
    reject_unapproved_.store(
        per_process::cli_options->force_fips_crypto &&
            per_process::cli_options->force_fips_crypto_policy == "strict",
        std::memory_order_release);
    std::call_once(install_once_, [this]() {
      OSSL_INDICATOR_get_callback(nullptr, &previous_callback_);
      OSSL_INDICATOR_set_callback(nullptr, OnOpenSSLIndicator);
    });
  }

  void Setup(Environment* env) {
    CHECK(env->owns_process_state());
    auto channel =
        diagnostics_channel::Channel::Get(env, kFipsIndicatorChannel);
    if (!channel) return;

    Realm* realm = env->principal_realm();
    auto* binding = realm->GetBindingData<diagnostics_channel::BindingData>();
    CHECK_NOT_NULL(binding);
    const uint32_t index =
        binding->GetOrCreateChannelIndex(std::string(kFipsIndicatorChannel));

    {
      Mutex::ScopedLock lock(mutex_);
      CHECK_NULL(env_);
      env_ = env;
      channel_ = channel;
    }
    env->AddCleanupHook(Cleanup, this);
    binding->SetChannelStatusCallback(
        index, [this](bool active) { SetActive(active); });
    SetActive(channel->HasSubscribers());
  }

 private:
  void SetActive(bool active) {
    subscription_generation_++;
    active_.store(active, std::memory_order_release);
    if (active) return;

    {
      Mutex::ScopedLock lock(mutex_);
      events_.clear();
      dropped_events_ = 0;
    }
  }

  static int OnOpenSSLIndicator(const char* operation,
                                const char* reason,
                                const OSSL_PARAM* params) {
    return Get().OnIndicator(operation, reason, params);
  }

  static void Cleanup(void* data) {
    static_cast<FipsIndicatorState*>(data)->CleanupEnvironment();
  }

  int OnIndicator(const char* operation,
                  const char* reason,
                  const OSSL_PARAM* params) {
    const int previous_result =
        previous_callback_ == nullptr
            ? 1
            : previous_callback_(operation, reason, params);
    const int result = reject_unapproved_.load(std::memory_order_acquire)
                           ? 0
                           : previous_result;
    if (!active_.load(std::memory_order_acquire)) return result;

    const bool blocked = result == 0;
    {
      Mutex::ScopedLock lock(mutex_);
      if (env_ != nullptr && active_.load(std::memory_order_relaxed)) {
        const std::string operation_string =
            operation == nullptr ? "" : operation;
        const std::string reason_string = reason == nullptr ? "" : reason;
        const auto existing = std::find_if(
            events_.begin(),
            events_.end(),
            [&](const FipsIndicatorEvent& event) {
              return event.operation == operation_string &&
                     event.reason == reason_string && event.blocked == blocked;
            });
        if (existing == events_.end()) {
          if (events_.size() < kMaxPendingFipsIndicatorEvents) {
            events_.push_back({operation_string, reason_string, blocked});
          } else if (dropped_events_ != UINT32_MAX) {
            dropped_events_++;
          }
        } else if (existing->count != UINT32_MAX) {
          existing->count++;
        } else if (dropped_events_ != UINT32_MAX) {
          dropped_events_++;
        }
        if (!dispatch_scheduled_) {
          dispatch_scheduled_ = true;
          env_->SetImmediateThreadsafe(
              [](Environment* env) { Get().Drain(env); },
              CallbackFlags::kUnrefed);
        }
      }
    }
    return result;
  }

  void Drain(Environment* env) {
    CHECK(env->owns_process_state());
    std::deque<FipsIndicatorEvent> events;
    {
      Mutex::ScopedLock lock(mutex_);
      if (env_ != env) return;
      events.swap(events_);
      if (!events.empty()) events.front().dropped = dropped_events_;
      dropped_events_ = 0;
      dispatch_scheduled_ = false;
    }
    if (events.empty() || !channel_ || !channel_->HasSubscribers()) return;

    Isolate* isolate = env->isolate();
    HandleScope handle_scope(isolate);
    Local<Context> context = env->context();
    const uint64_t subscription_generation = subscription_generation_;
    for (const auto& event : events) {
      if (subscription_generation_ != subscription_generation) return;
      MaybeLocal<Value> values[] = {
          OneByteString(isolate, event.operation),
          OneByteString(isolate, event.reason),
          v8::Boolean::New(isolate, event.blocked),
          Uint32::New(isolate, event.count),
          Uint32::New(isolate, event.dropped),
      };
      Local<Object> value;
      if (!NewDictionaryInstance(
               context, GetFipsIndicatorEventTemplate(env), values)
               .ToLocal(&value)) {
        return;
      }
      channel_->Publish(env, value);
      if (subscription_generation_ != subscription_generation) return;
    }
  }

  void CleanupEnvironment() {
    subscription_generation_++;
    active_.store(false, std::memory_order_release);
    {
      Mutex::ScopedLock lock(mutex_);
      env_ = nullptr;
      channel_.reset();
      events_.clear();
      dropped_events_ = 0;
      dispatch_scheduled_ = false;
    }
  }

  std::once_flag install_once_;
  std::atomic<bool> active_{false};
  std::atomic<bool> reject_unapproved_{false};
  OSSL_INDICATOR_CALLBACK* previous_callback_ = nullptr;
  Mutex mutex_;
  Environment* env_ = nullptr;
  BaseObjectPtr<diagnostics_channel::Channel> channel_;
  std::deque<FipsIndicatorEvent> events_;
  uint32_t dropped_events_ = 0;
  bool dispatch_scheduled_ = false;
  uint64_t subscription_generation_ = 0;
};

}  // namespace
#endif

void InstallFipsIndicatorCallback() {
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 4)
  const auto& options = per_process::cli_options;
  const bool strict = options->force_fips_crypto &&
                      options->force_fips_crypto_policy == "strict";
  if (options->enable_fips_indicator_events || strict) {
    FipsIndicatorState::Get().Install();
  }
#endif
}

void SetupFipsIndicatorChannel(const FunctionCallbackInfo<Value>& args) {
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 4)
  Environment* env = Environment::GetCurrent(args);
  if (env->owns_process_state() &&
      per_process::cli_options->enable_fips_indicator_events) {
    FipsIndicatorState::Get().Setup(env);
  }
#else
  USE(args);
#endif
}

std::optional<std::string> ProcessFipsOptions() {
  const bool enable_fips = per_process::cli_options->enable_fips_crypto;
  const bool force_fips = per_process::cli_options->force_fips_crypto;
  if (!enable_fips && !force_fips) return std::nullopt;

#ifndef OPENSSL_IS_BORINGSSL
  // Whether FIPS-approved implementations are reachable is decided by the
  // OpenSSL configuration, not by Node.js. Refuse to start rather than
  // restrict the default property query to a provider that is not there,
  // which would leave every operation failing as unsupported.
  if (!ncrypto::testFipsEnabled()) {
    const std::string option = force_fips ? "--force-fips" : "--enable-fips";
    return option + " requires an active OpenSSL provider named \"fips\". "
                    "FIPS mode is configured through OpenSSL; see "
                    "https://nodejs.org/api/crypto.html#fips-mode";
  }
#endif

  CryptoErrorList errors{CryptoErrorList::Option::NONE};
  if (!ncrypto::setFipsEnabled(true, &errors)) {
    std::string error = "OpenSSL error when trying to enable FIPS";
    if (!errors.empty()) error += ':';
    for (const auto& openssl_error : errors) {
      error += '\n';
      error += openssl_error;
    }
    return error;
  }

  return std::nullopt;
}

bool InitCryptoOnce(Isolate* isolate) {
  static uv_once_t init_once = UV_ONCE_INIT;
  TryCatch try_catch{isolate};
  uv_once(&init_once, InitCryptoOnce);
  if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
    try_catch.ReThrow();
    return false;
  }
  return true;
}

// Protect accesses to FIPS state with a mutex. This should potentially
// be part of a larger mutex for global OpenSSL state.
static Mutex fips_mutex;

bool IsFipsEnabled() {
  Mutex::ScopedLock fips_lock(fips_mutex);
  return ncrypto::isFipsEnabled();
}

void InitCryptoOnce() {
  Mutex::ScopedLock lock(per_process::cli_options_mutex);
  Mutex::ScopedLock fips_lock(fips_mutex);
#ifndef OPENSSL_IS_BORINGSSL
  OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new();
  CHECK_NOT_NULL(settings);

  // --openssl-legacy-provider
  if (per_process::cli_options->openssl_legacy_provider) {
    OSSL_PROVIDER* legacy_provider = OSSL_PROVIDER_load(nullptr, "legacy");
    if (legacy_provider == nullptr) {
      fprintf(stderr, "Unable to load legacy provider.\n");
    }
  }

  OPENSSL_init_ssl(0, settings);
  InstallFipsIndicatorCallback();

  ncrypto::ConfigurePqcEncoding();
  OPENSSL_INIT_free(settings);
  settings = nullptr;

#ifndef _WIN32
  if (per_process::cli_options->secure_heap != 0) {
    switch (DataPointer::TryInitSecureHeap(
        per_process::cli_options->secure_heap,
        per_process::cli_options->secure_heap_min)) {
      case DataPointer::InitSecureHeapResult::FAILED:
        fprintf(stderr, "Unable to initialize openssl secure heap.\n");
        break;
      case DataPointer::InitSecureHeapResult::UNABLE_TO_MEMORY_MAP:
        // Not a fatal error but worthy of a warning.
        fprintf(stderr, "Unable to memory map openssl secure heap.\n");
        break;
      case DataPointer::InitSecureHeapResult::OK:
        // OK!
        break;
    }
  }
#endif

#endif  // OPENSSL_IS_BORINGSSL

  // Turn off compression. Saves memory and protects against CRIME attacks.
  // No-op with OPENSSL_NO_COMP builds of OpenSSL.
  sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
}

void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
  Mutex::ScopedLock lock(per_process::cli_options_mutex);
  args.GetReturnValue().Set(IsFipsEnabled() ? 1 : 0);
}

void GetFipsCryptoGeneration(const FunctionCallbackInfo<Value>& args) {
  args.GetReturnValue().Set(BigInt::NewFromUnsigned(
      args.GetIsolate(), ncrypto::getFipsStateGeneration()));
}

void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
  Mutex::ScopedLock lock(per_process::cli_options_mutex);
  Mutex::ScopedLock fips_lock(fips_mutex);

  CHECK(!per_process::cli_options->force_fips_crypto);
  Environment* env = Environment::GetCurrent(args);
  CHECK(env->owns_process_state());
  bool enable = args[0]->BooleanValue(env->isolate());

  CryptoErrorList errors;
  if (!ncrypto::setFipsEnabled(enable, &errors)) {
    Local<Value> exception;
    if (cryptoErrorListToException(env, errors).ToLocal(&exception)) {
      env->isolate()->ThrowException(exception);
    }
  }
}

void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args) {
  Mutex::ScopedLock lock(per_process::cli_options_mutex);
  Mutex::ScopedLock fips_lock(fips_mutex);
  args.GetReturnValue().Set(ncrypto::testFipsEnabled() ? 1 : 0);
}

void GetOpenSSLSecLevelCrypto(const FunctionCallbackInfo<Value>& args) {
  ncrypto::ClearErrorOnReturn clear_error_on_return;
  if (auto sec_level = SSLPointer::getSecurityLevel()) {
    return args.GetReturnValue().Set(sec_level.value());
  }
  Environment* env = Environment::GetCurrent(args);
  ThrowCryptoError(
      env, clear_error_on_return.peekError(), "getOpenSSLSecLevel");
}

void CryptoErrorStore::Capture() {
  errors_.clear();
  primary_openssl_error_ = 0;
  while (const uint32_t err = ERR_get_error()) {
    if (primary_openssl_error_ == 0) primary_openssl_error_ = err;
    char buf[256];
    ERR_error_string_n(err, buf, sizeof(buf));
    errors_.emplace_back(buf);
  }
  std::ranges::reverse(errors_);
}

bool CryptoErrorStore::Empty() const {
  return errors_.empty();
}

MaybeLocal<Value> cryptoErrorListToException(Environment* env,
                                             const CryptoErrorList& errors) {
  // The CryptoErrorList contains a listing of zero or more errors.
  // If there are no errors, it is likely a bug but we will return
  // an error anyway.
  if (errors.empty()) {
    return Exception::Error(env->ok_string());
  }

  // The last error in the list is the one that will be used as the
  // error message. All other errors will be added to the .opensslErrorStack
  // property. We know there has to be at least one error in the list at
  // this point.
  auto& last = errors.peek_back();
  Local<String> message;
  if (!String::NewFromUtf8(
           env->isolate(), last.data(), NewStringType::kNormal, last.size())
           .ToLocal(&message)) {
    return {};
  }

  Local<Value> exception = Exception::Error(message);
  CHECK(!exception.IsEmpty());

  if (errors.size() > 1) {
    CHECK(exception->IsObject());
    Local<Object> exception_obj = exception.As<Object>();
    LocalVector<Value> stack(env->isolate());
    stack.reserve(errors.size() - 1);

    // Iterate over all but the last error in the list.
    auto current = errors.begin();
    auto last = errors.end();
    last--;
    while (current != last) {
      Local<Value> error;
      if (!ToV8Value(env->context(), *current).ToLocal(&error)) {
        return {};
      }
      stack.push_back(error);
      ++current;
    }

    Local<v8::Array> stackArray =
        v8::Array::New(env->isolate(), stack.data(), stack.size());

    if (exception_obj
            ->Set(env->context(), env->openssl_error_stack(), stackArray)
            .IsNothing()) {
      return {};
    }
  }
  return exception;
}

namespace error {
v8::Maybe<void> Decorate(Environment* env,
                         v8::Local<v8::Object> obj,
                         unsigned long err);  // NOLINT(runtime/int)
}  // namespace error

MaybeLocal<Value> CryptoErrorStore::ToException(
    Environment* env,
    Local<String> exception_string) const {
  if (exception_string.IsEmpty()) {
    CryptoErrorStore copy(*this);
    if (copy.Empty()) {
      // But possibly a bug...
      copy.Insert(NodeCryptoError::OK);
    }
    // Use last element as the error message, everything else goes
    // into the .opensslErrorStack property on the exception object.
    const std::string& last_error_string = copy.errors_.back();
    Local<Value> exception_string;
    if (!ToV8Value(env->context(), last_error_string)
             .ToLocal(&exception_string)) {
      return MaybeLocal<Value>();
    }
    DCHECK(exception_string->IsString());
    copy.errors_.pop_back();
    return copy.ToException(env, exception_string.As<v8::String>());
  }

  Local<Value> exception_v = Exception::Error(exception_string);
  CHECK(!exception_v.IsEmpty());
  CHECK(exception_v->IsObject());
  Local<Object> exception = exception_v.As<Object>();

  if (!Empty()) {
    Local<Value> stack;
    if (!ToV8Value(env->context(), errors_).ToLocal(&stack) ||
        exception->Set(env->context(), env->openssl_error_stack(), stack)
            .IsNothing()) {
      return MaybeLocal<Value>();
    }
  }

  if (primary_openssl_error_ != 0) {
    if (error::Decorate(env, exception, primary_openssl_error_).IsNothing()) {
      return MaybeLocal<Value>();
    }
  } else if (node_error_code_ != nullptr) {
    if (exception
            ->Set(env->context(),
                  env->code_string(),
                  OneByteString(env->isolate(), node_error_code_))
            .IsNothing()) {
      return MaybeLocal<Value>();
    }
  }

  return exception_v;
}

ByteSource::ByteSource(ByteSource&& other) noexcept
    : data_(other.data_),
      allocated_data_(other.allocated_data_),
      size_(other.size_) {
  other.allocated_data_ = nullptr;
}

ByteSource::~ByteSource() {
  OPENSSL_clear_free(allocated_data_, size_);
}

ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
  if (&other != this) {
    OPENSSL_clear_free(allocated_data_, size_);
    data_ = other.data_;
    allocated_data_ = other.allocated_data_;
    other.allocated_data_ = nullptr;
    size_ = other.size_;
  }
  return *this;
}

void TruncateToBitLength(size_t length_bits, ByteSource* bytes) {
  CHECK_NOT_NULL(bytes);
  const size_t length_bytes = NumBitsToBytes(length_bits);
  CHECK_LE(length_bytes, bytes->size());

  if (bytes->allocated_data_ == nullptr || bytes->size() != length_bytes) {
    auto data = DataPointer::Alloc(length_bytes);
    if (length_bytes > 0) {
      CHECK_NOT_NULL(data.get());
      memcpy(data.get(), bytes->data(), length_bytes);
    }
    *bytes = ByteSource::Allocated(data.release());
  }

  const size_t remainder_bits = length_bits % CHAR_BIT;
  if (remainder_bits != 0) {
    auto* data = static_cast<unsigned char*>(bytes->allocated_data_);
    CHECK_NOT_NULL(data);
    const unsigned char mask =
        static_cast<unsigned char>(0xff << (CHAR_BIT - remainder_bits));
    data[length_bytes - 1] &= mask;
  }
}

std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(
    Environment* env) {
  // It's ok for allocated_data_ to be nullptr but
  // only if size_ is zero.
  CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr);
  std::unique_ptr<BackingStore> ptr = AdoptIntoBackingStore(
      env->isolate(),
      allocated_data_,
      size(),
      [](void* data, size_t length, void*) {
        OPENSSL_clear_free(data, length);
      },
      nullptr);
  allocated_data_ = nullptr;
  data_ = nullptr;
  size_ = 0;
  if (!ptr) THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
  return ptr;
}

Local<ArrayBuffer> ByteSource::ToArrayBuffer(Environment* env) {
  std::unique_ptr<BackingStore> store = ReleaseToBackingStore(env);
  return ArrayBuffer::New(env->isolate(), std::move(store));
}

MaybeLocal<Uint8Array> ByteSource::ToBuffer(Environment* env) {
  Local<ArrayBuffer> ab = ToArrayBuffer(env);
  return Buffer::New(env, ab, 0, ab->ByteLength());
}

ByteSource ByteSource::FromBIO(const BIOPointer& bio) {
  CHECK(bio);
  BUF_MEM* bptr = bio;
  auto out = DataPointer::Alloc(bptr->length);
  memcpy(out.get(), bptr->data, bptr->length);
  return ByteSource::Allocated(out.release());
}

ByteSource ByteSource::FromEncodedString(Environment* env,
                                         Local<String> key,
                                         enum encoding enc) {
  size_t length = 0;
  ByteSource out;

  if (StringBytes::Size(env->isolate(), key, enc).To(&length) && length > 0) {
    auto buf = DataPointer::Alloc(length);
    size_t actual = StringBytes::Write(
        env->isolate(), static_cast<char*>(buf.get()), length, key, enc);
    out = ByteSource::Allocated(buf.resize(actual).release());
  }

  return out;
}

ByteSource ByteSource::FromStringOrBuffer(Environment* env,
                                          Local<Value> value) {
  return IsAnyBufferSource(value) ? FromBuffer(value)
                                  : FromString(env, value.As<String>());
}

ByteSource ByteSource::FromString(Environment* env, Local<String> str,
                                  bool ntc) {
  CHECK(str->IsString());
  size_t size = str->Utf8LengthV2(env->isolate());
  size_t alloc_size = ntc ? size + 1 : size;
  auto out = DataPointer::Alloc(alloc_size);
  int flags = String::WriteFlags::kNone;
  if (ntc) flags |= String::WriteFlags::kNullTerminate;
  str->WriteUtf8V2(
      env->isolate(), static_cast<char*>(out.get()), alloc_size, flags);
  return ByteSource::Allocated(out.release());
}

ByteSource ByteSource::FromBuffer(Local<Value> buffer, bool ntc) {
  ArrayBufferOrViewContents<char> buf(buffer);
  return ntc ? buf.ToNullTerminatedCopy() : buf.ToByteSource();
}

ByteSource ByteSource::FromSecretKeyBytes(
    Environment* env,
    Local<Value> value) {
  // JS normalizes secret KeyObject/CryptoKey inputs to a KeyObjectHandle.
  // Strings are converted here instead of in JS to avoid creating an
  // unprotected copy on the heap.
  return value->IsString() || IsAnyBufferSource(value)
             ? ByteSource::FromStringOrBuffer(env, value)
             : ByteSource::FromSymmetricKeyObjectHandle(value);
}

ByteSource ByteSource::NullTerminatedCopy(Environment* env,
                                          Local<Value> value) {
  return Buffer::HasInstance(value) ? FromBuffer(value, true)
                                    : FromString(env, value.As<String>(), true);
}

ByteSource ByteSource::FromSymmetricKeyObjectHandle(Local<Value> handle) {
  CHECK(handle->IsObject());
  KeyObjectHandle* key =
      BaseObject::Unwrap<KeyObjectHandle>(handle.As<Object>());
  CHECK_NOT_NULL(key);
  return Foreign(key->Data().GetSymmetricKey(),
                 key->Data().GetSymmetricKeySize());
}

ByteSource ByteSource::Allocated(void* data, size_t size) {
  return ByteSource(data, data, size);
}

ByteSource ByteSource::Foreign(const void* data, size_t size) {
  return ByteSource(data, nullptr, size);
}

namespace error {
Maybe<void> Decorate(Environment* env,
                     Local<Object> obj,
                     unsigned long err) {  // NOLINT(runtime/int)
  if (err == 0) return JustVoid();         // No decoration necessary.

  const char* ls = ERR_lib_error_string(err);
#if NCRYPTO_USE_OPENSSL_PROVIDER
  const char* fs = nullptr;
#elif NCRYPTO_USE_BORINGSSL
  const char* fs = ERR_func_error_string(err);
#else
#error "Unsupported crypto backend"
#endif
  const char* rs = ERR_reason_error_string(err);

  Isolate* isolate = env->isolate();
  Local<Context> context = isolate->GetCurrentContext();

  if (ls != nullptr) {
    if (obj->Set(context, env->library_string(),
                 OneByteString(isolate, ls)).IsNothing()) {
      return Nothing<void>();
    }
  }
  if (fs != nullptr) {
    if (obj->Set(context, env->function_string(),
                 OneByteString(isolate, fs)).IsNothing()) {
      return Nothing<void>();
    }
  }
  if (rs != nullptr) {
    if (obj->Set(context, env->reason_string(),
                 OneByteString(isolate, rs)).IsNothing()) {
      return Nothing<void>();
    }

    // SSL has no API to recover the error name from the number, so we
    // transform reason strings like "this error" to "ERR_SSL_THIS_ERROR",
    // which ends up being close to the original error macro name.
    std::string reason(rs);

    for (auto& c : reason) {
      if (c == ' ')
        c = '_';
      else
        c = ToUpper(c);
    }

#ifdef OPENSSL_IS_BORINGSSL
#define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V)
#else
#define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V)                                   \
  V(PKCS12)                                                                    \
  V(DSO)                                                                       \
  V(OSSL_STORE)                                                                \
  V(FIPS)                                                                      \
  V(TS)                                                                        \
  V(CT)                                                                        \
  V(ASYNC)                                                                     \
  V(KDF)                                                                       \
  V(SM2)
#endif

#define OSSL_ERROR_CODES_MAP(V)                                                \
  V(SYS)                                                                       \
  V(BN)                                                                        \
  V(RSA)                                                                       \
  V(DH)                                                                        \
  V(EVP)                                                                       \
  V(BUF)                                                                       \
  V(OBJ)                                                                       \
  V(PEM)                                                                       \
  V(DSA)                                                                       \
  V(X509)                                                                      \
  V(ASN1)                                                                      \
  V(CONF)                                                                      \
  V(CRYPTO)                                                                    \
  V(EC)                                                                        \
  V(SSL)                                                                       \
  V(BIO)                                                                       \
  V(PKCS7)                                                                     \
  V(X509V3)                                                                    \
  V(RAND)                                                                      \
  V(ENGINE)                                                                    \
  V(OCSP)                                                                      \
  V(UI)                                                                        \
  V(COMP)                                                                      \
  V(ECDSA)                                                                     \
  V(ECDH)                                                                      \
  V(CMS)                                                                       \
  V(HMAC)                                                                      \
  V(USER)                                                                      \
  OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V)

#define V(name) case ERR_LIB_##name: lib = #name "_"; break;
    const char* lib = "";
    const char* prefix = "OSSL_";
    switch (ERR_GET_LIB(err)) { /* NOLINT(whitespace/newline) */
      OSSL_ERROR_CODES_MAP(V)
    }
#undef V
#undef OSSL_ERROR_CODES_MAP
#undef OSSL_ERROR_CODES_MAP_OPENSSL_ONLY
    // Don't generate codes like "ERR_OSSL_SSL_".
    if (lib && strcmp(lib, "SSL_") == 0)
      prefix = "";

    // All OpenSSL reason strings fit in a single 80-column macro definition,
    // all prefix lengths are <= 10, and ERR_OSSL_ is 9, so 128 is more than
    // sufficient.
    char code[128];
    snprintf(code, sizeof(code), "ERR_%s%s%s", prefix, lib, reason.c_str());

    if (obj->Set(env->isolate()->GetCurrentContext(),
             env->code_string(),
             OneByteString(env->isolate(), code)).IsNothing())
      return Nothing<void>();
  }

  return JustVoid();
}
}  // namespace error

void ThrowCryptoError(Environment* env,
                      unsigned long err,  // NOLINT(runtime/int)
                      // Default, only used if there is no SSL `err` which can
                      // be used to create a long-style message string.
                      const char* message) {
  char message_buffer[128] = {0};
  if (err != 0 || message == nullptr) {
    ERR_error_string_n(err, message_buffer, sizeof(message_buffer));
    message = message_buffer;
  }
  HandleScope scope(env->isolate());
  Local<String> exception_string;
  Local<Value> exception;
  Local<Object> obj;
  if (!String::NewFromUtf8(env->isolate(), message).ToLocal(&exception_string))
    return;
  CryptoErrorStore errors;
  errors.Capture();
  if (!errors.ToException(env, exception_string).ToLocal(&exception) ||
      !exception->ToObject(env->context()).ToLocal(&obj) ||
      error::Decorate(env, obj, err).IsNothing()) {
    return;
  }
  env->isolate()->ThrowException(exception);
}

MaybeLocal<Value> EncodeBignum(Environment* env, const BIGNUM* bn, int size) {
  EscapableHandleScope scope(env->isolate());
  auto buf = BignumPointer::EncodePadded(bn, size);
  CHECK_EQ(buf.size(), static_cast<size_t>(size));
  Local<Value> ret;
  if (!StringBytes::Encode(env->isolate(),
                           reinterpret_cast<const char*>(buf.get()),
                           buf.size(),
                           BASE64URL)
           .ToLocal(&ret)) {
    return {};
  }
  return scope.Escape(ret);
}

Maybe<void> SetEncodedValue(Environment* env,
                            Local<Object> target,
                            Local<String> name,
                            const BIGNUM* bn,
                            int size) {
  Local<Value> value;
  CHECK_NOT_NULL(bn);
  if (size == 0) size = BignumPointer::GetByteCount(bn);
  if (!EncodeBignum(env, bn, size).ToLocal(&value)) {
    return Nothing<void>();
  }
  return target->DefineOwnProperty(env->context(), name, value).FromMaybe(false)
             ? JustVoid()
             : Nothing<void>();
}

CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args) {
  CHECK(args->IsUint32());
  uint32_t mode = args.As<v8::Uint32>()->Value();
  CHECK_LE(mode, kCryptoJobWebCrypto);
  return static_cast<CryptoJobMode>(mode);
}

bool IsCryptoJobAsync(CryptoJobMode mode) {
  return mode == kCryptoJobAsync || mode == kCryptoJobWebCrypto;
}

MaybeLocal<Value> CreateWebCryptoJobError(Environment* env,
                                          Local<Value> cause) {
  Isolate* isolate = env->isolate();
  Local<Context> context = env->context();
  Local<Object> per_context_bindings;
  Local<Value> domexception_ctor;
  if (!GetPerContextExports(context).ToLocal(&per_context_bindings) ||
      !per_context_bindings
           ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "DOMException"))
           .ToLocal(&domexception_ctor)) {
    return {};
  }
  CHECK(domexception_ctor->IsFunction());

  Local<Object> options = Object::New(isolate);
  if (options->Set(context, env->name_string(), env->operationerror_string())
          .IsNothing() ||
      options->Set(context, env->cause_string(), cause).IsNothing()) {
    return {};
  }

  Local<Value> argv[] = {
      FIXED_ONE_BYTE_STRING(isolate,
                            "The operation failed for an operation-specific "
                            "reason"),
      options,
  };

  return domexception_ctor.As<Function>()->NewInstance(
      context, arraysize(argv), argv);
}

MaybeLocal<Value> ToWebCryptoJobResult(Environment* env, Local<Value> value) {
  if (value->IsArrayBuffer()) {
    return value;
  }

  if (Buffer::HasInstance(value)) {
    return value.As<ArrayBufferView>()->Buffer();
  }

  CHECK(value->IsBoolean() || (value->IsObject() && !value->IsArray() &&
                               !value->IsArrayBufferView()));
  return value;
}

namespace {
// SecureBuffer uses OpenSSL's secure heap feature to allocate a
// Uint8Array. Without --secure-heap, OpenSSL's secure heap is disabled,
// in which case this has the same semantics as
// using OPENSSL_malloc. However, if the secure heap is
// initialized, SecureBuffer will automatically use it.
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args[0]->IsUint32());
  uint32_t len = args[0].As<Uint32>()->Value();

  auto data = DataPointer::SecureAlloc(len);
  if (!data) {
    return THROW_ERR_OPERATION_FAILED(env, "Allocation failed");
  }
  auto released = data.release();

  // Under V8_ENABLE_SANDBOX this ends up as a plain copy, which is fine:
  // --secure-heap is unavailable there, so SecureAlloc() is OPENSSL_malloc().
  std::shared_ptr<BackingStore> store = AdoptIntoBackingStore(
      env->isolate(),
      released.data,
      released.len,
      [](void* data, size_t len, void* deleter_data) {
        // The DataPointer takes ownership and will appropriately
        // free the data when it gets reset.
        DataPointer free_me(
            ncrypto::Buffer<void>{
                .data = data,
                .len = len,
            },
            true);
      },
      nullptr);
  if (!store) {
    return THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
  }

  Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
  args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
}

void SecureHeapUsed(const FunctionCallbackInfo<Value>& args) {
  args.GetReturnValue().Set(
      BigInt::New(args.GetIsolate(), DataPointer::GetSecureHeapUsed()));
}
}  // namespace

namespace Util {
void Initialize(Environment* env, Local<Object> target) {
  Local<Context> context = env->context();
  SetMethodNoSideEffect(context, target, "getFipsCrypto", GetFipsCrypto);
  SetMethodNoSideEffect(
      context, target, "getFipsCryptoGeneration", GetFipsCryptoGeneration);
  SetMethod(
      context, target, "setupFipsIndicatorChannel", SetupFipsIndicatorChannel);
  SetMethod(context, target, "setFipsCrypto", SetFipsCrypto);
  SetMethodNoSideEffect(context, target, "testFipsCrypto", TestFipsCrypto);

  NODE_DEFINE_CONSTANT(target, kCryptoJobAsync);
  NODE_DEFINE_CONSTANT(target, kCryptoJobSync);
  NODE_DEFINE_CONSTANT(target, kCryptoJobWebCrypto);

  SetMethod(context, target, "secureBuffer", SecureBuffer);
  SetMethodNoSideEffect(context, target, "secureHeapUsed", SecureHeapUsed);

  SetMethodNoSideEffect(
      context, target, "getOpenSSLSecLevelCrypto", GetOpenSSLSecLevelCrypto);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  registry->Register(GetFipsCrypto);
  registry->Register(GetFipsCryptoGeneration);
  registry->Register(SetupFipsIndicatorChannel);
  registry->Register(SetFipsCrypto);
  registry->Register(TestFipsCrypto);
  registry->Register(SecureBuffer);
  registry->Register(SecureHeapUsed);
  registry->Register(GetOpenSSLSecLevelCrypto);
}

}  // namespace Util

}  // namespace crypto
}  // namespace node