%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/crypto_hash.cc

#include "crypto/crypto_hash.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"

#include <cstdio>

namespace node {

using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;

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

void Hash::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
  tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
}

#if OPENSSL_VERSION_MAJOR >= 3
void PushAliases(const char* name, void* data) {
  static_cast<std::vector<std::string>*>(data)->push_back(name);
}

EVP_MD* GetCachedMDByID(Environment* env, size_t id) {
  CHECK_LT(id, env->evp_md_cache.size());
  EVP_MD* result = env->evp_md_cache[id].get();
  CHECK_NOT_NULL(result);
  return result;
}

struct MaybeCachedMD {
  EVP_MD* explicit_md = nullptr;
  const EVP_MD* implicit_md = nullptr;
  int32_t cache_id = -1;
};

MaybeCachedMD FetchAndMaybeCacheMD(Environment* env, const char* search_name) {
  const EVP_MD* implicit_md = EVP_get_digestbyname(search_name);
  if (!implicit_md) return {nullptr, nullptr, -1};

  const char* real_name = EVP_MD_get0_name(implicit_md);
  if (!real_name) return {nullptr, implicit_md, -1};

  auto it = env->alias_to_md_id_map.find(real_name);
  if (it != env->alias_to_md_id_map.end()) {
    size_t id = it->second;
    return {GetCachedMDByID(env, id), implicit_md, static_cast<int32_t>(id)};
  }

  // EVP_*_fetch() does not support alias names, so we need to pass it the
  // real/original algorithm name.
  // We use EVP_*_fetch() as a filter here because it will only return an
  // instance if the algorithm is supported by the public OpenSSL APIs (some
  // algorithms are used internally by OpenSSL and are also passed to this
  // callback).
  EVP_MD* explicit_md = EVP_MD_fetch(nullptr, real_name, nullptr);
  if (!explicit_md) return {nullptr, implicit_md, -1};

  // Cache the EVP_MD* fetched.
  env->evp_md_cache.emplace_back(explicit_md);
  size_t id = env->evp_md_cache.size() - 1;

  // Add all the aliases to the map to speed up next lookup.
  std::vector<std::string> aliases;
  EVP_MD_names_do_all(explicit_md, PushAliases, &aliases);
  for (const auto& alias : aliases) {
    env->alias_to_md_id_map.emplace(alias, id);
  }
  env->alias_to_md_id_map.emplace(search_name, id);

  return {explicit_md, implicit_md, static_cast<int32_t>(id)};
}

void SaveSupportedHashAlgorithmsAndCacheMD(const EVP_MD* md,
                                           const char* from,
                                           const char* to,
                                           void* arg) {
  if (!from) return;
  Environment* env = static_cast<Environment*>(arg);
  auto result = FetchAndMaybeCacheMD(env, from);
  if (result.explicit_md) {
    env->supported_hash_algorithms.push_back(from);
  }
}

#else
void SaveSupportedHashAlgorithms(const EVP_MD* md,
                                 const char* from,
                                 const char* to,
                                 void* arg) {
  if (!from) return;
  Environment* env = static_cast<Environment*>(arg);
  env->supported_hash_algorithms.push_back(from);
}
#endif  // OPENSSL_VERSION_MAJOR >= 3

const std::vector<std::string>& GetSupportedHashAlgorithms(Environment* env) {
  if (env->supported_hash_algorithms.empty()) {
    MarkPopErrorOnReturn mark_pop_error_on_return;
#if OPENSSL_VERSION_MAJOR >= 3
    // Since we'll fetch the EVP_MD*, cache them along the way to speed up
    // later lookups instead of throwing them away immediately.
    EVP_MD_do_all_sorted(SaveSupportedHashAlgorithmsAndCacheMD, env);
#else
    EVP_MD_do_all_sorted(SaveSupportedHashAlgorithms, env);
#endif
  }
  return env->supported_hash_algorithms;
}

void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
  Local<Context> context = args.GetIsolate()->GetCurrentContext();
  Environment* env = Environment::GetCurrent(context);
  const std::vector<std::string>& results = GetSupportedHashAlgorithms(env);

  Local<Value> ret;
  if (ToV8Value(context, results).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void Hash::GetCachedAliases(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = args.GetIsolate()->GetCurrentContext();
  Environment* env = Environment::GetCurrent(context);
  std::vector<Local<Name>> names;
  std::vector<Local<Value>> values;
  size_t size = env->alias_to_md_id_map.size();
#if OPENSSL_VERSION_MAJOR >= 3
  names.reserve(size);
  values.reserve(size);
  for (auto& [alias, id] : env->alias_to_md_id_map) {
    names.push_back(OneByteString(isolate, alias.c_str(), alias.size()));
    values.push_back(v8::Uint32::New(isolate, id));
  }
#else
  CHECK(env->alias_to_md_id_map.empty());
#endif
  Local<Value> prototype = v8::Null(isolate);
  Local<Object> result =
      Object::New(isolate, prototype, names.data(), values.data(), size);
  args.GetReturnValue().Set(result);
}

const EVP_MD* GetDigestImplementation(Environment* env,
                                      Local<Value> algorithm,
                                      Local<Value> cache_id_val,
                                      Local<Value> algorithm_cache) {
  CHECK(algorithm->IsString());
  CHECK(cache_id_val->IsInt32());
  CHECK(algorithm_cache->IsObject());

#if OPENSSL_VERSION_MAJOR >= 3
  int32_t cache_id = cache_id_val.As<Int32>()->Value();
  if (cache_id != -1) {  // Alias already cached, return the cached EVP_MD*.
    return GetCachedMDByID(env, cache_id);
  }

  // Only decode the algorithm when we don't have it cached to avoid
  // unnecessary overhead.
  Isolate* isolate = env->isolate();
  Utf8Value utf8(isolate, algorithm);

  auto result = FetchAndMaybeCacheMD(env, *utf8);
  if (result.cache_id != -1) {
    // Add the alias to both C++ side and JS side to speedup the lookup
    // next time.
    env->alias_to_md_id_map.emplace(*utf8, result.cache_id);
    if (algorithm_cache.As<Object>()
            ->Set(isolate->GetCurrentContext(),
                  algorithm,
                  v8::Int32::New(isolate, result.cache_id))
            .IsNothing()) {
      return nullptr;
    }
  }

  return result.explicit_md ? result.explicit_md : result.implicit_md;
#else
  Utf8Value utf8(env->isolate(), algorithm);
  return EVP_get_digestbyname(*utf8);
#endif
}

void Hash::Initialize(Environment* env, Local<Object> target) {
  Isolate* isolate = env->isolate();
  Local<Context> context = env->context();
  Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);

  t->InstanceTemplate()->SetInternalFieldCount(Hash::kInternalFieldCount);

  SetProtoMethod(isolate, t, "update", HashUpdate);
  SetProtoMethod(isolate, t, "digest", HashDigest);

  SetConstructorFunction(context, target, "Hash", t);

  SetMethodNoSideEffect(context, target, "getHashes", GetHashes);
  SetMethodNoSideEffect(context, target, "getCachedAliases", GetCachedAliases);

  HashJob::Initialize(env, target);

  SetMethodNoSideEffect(
      context, target, "internalVerifyIntegrity", InternalVerifyIntegrity);
}

void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  registry->Register(New);
  registry->Register(HashUpdate);
  registry->Register(HashDigest);
  registry->Register(GetHashes);
  registry->Register(GetCachedAliases);

  HashJob::RegisterExternalReferences(registry);

  registry->Register(InternalVerifyIntegrity);
}

// new Hash(algorithm, algorithmId, xofLen, algorithmCache)
void Hash::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  const Hash* orig = nullptr;
  const EVP_MD* md = nullptr;
  if (args[0]->IsObject()) {
    ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
    md = EVP_MD_CTX_md(orig->mdctx_.get());
  } else {
    md = GetDigestImplementation(env, args[0], args[2], args[3]);
  }

  Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
  if (!args[1]->IsUndefined()) {
    CHECK(args[1]->IsUint32());
    xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
  }

  Hash* hash = new Hash(env, args.This());
  if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
    return ThrowCryptoError(env, ERR_get_error(),
                            "Digest method not supported");
  }

  if (orig != nullptr &&
      0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
    return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
  }
}

bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
  mdctx_.reset(EVP_MD_CTX_new());
  if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
    mdctx_.reset();
    return false;
  }

  md_len_ = EVP_MD_size(md);
  if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
    // This is a little hack to cause createHash to fail when an incorrect
    // hashSize option was passed for a non-XOF hash function.
    if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
      EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
      return false;
    }
    md_len_ = xof_md_len.FromJust();
  }

  return true;
}

bool Hash::HashUpdate(const char* data, size_t len) {
  if (!mdctx_)
    return false;
  return EVP_DigestUpdate(mdctx_.get(), data, len) == 1;
}

void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
  Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
                        const char* data, size_t size) {
    Environment* env = Environment::GetCurrent(args);
    if (UNLIKELY(size > INT_MAX))
      return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
    bool r = hash->HashUpdate(data, size);
    args.GetReturnValue().Set(r);
  });
}

void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  Hash* hash;
  ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());

  enum encoding encoding = BUFFER;
  if (args.Length() >= 1) {
    encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
  }

  unsigned int len = hash->md_len_;

  // TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
  // platforms and will cause a segmentation fault if called. This workaround
  // causes hash.digest() to correctly return an empty buffer / string.
  // See https://github.com/openssl/openssl/issues/9431.

  if (!hash->digest_ && len > 0) {
    // Some hash algorithms such as SHA3 do not support calling
    // EVP_DigestFinal_ex more than once, however, Hash._flush
    // and Hash.digest can both be used to retrieve the digest,
    // so we need to cache it.
    // See https://github.com/nodejs/node/issues/28245.

    ByteSource::Builder digest(len);

    size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
    int ret;
    if (len == default_len) {
      ret = EVP_DigestFinal_ex(
          hash->mdctx_.get(), digest.data<unsigned char>(), &len);
      // The output length should always equal hash->md_len_
      CHECK_EQ(len, hash->md_len_);
    } else {
      ret = EVP_DigestFinalXOF(
          hash->mdctx_.get(), digest.data<unsigned char>(), len);
    }

    if (ret != 1)
      return ThrowCryptoError(env, ERR_get_error());

    hash->digest_ = std::move(digest).release();
  }

  Local<Value> error;
  MaybeLocal<Value> rc = StringBytes::Encode(
      env->isolate(), hash->digest_.data<char>(), len, encoding, &error);
  if (rc.IsEmpty()) {
    CHECK(!error.IsEmpty());
    env->isolate()->ThrowException(error);
    return;
  }
  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
}

HashConfig::HashConfig(HashConfig&& other) noexcept
    : mode(other.mode),
      in(std::move(other.in)),
      digest(other.digest),
      length(other.length) {}

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

void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
  // If the Job is sync, then the HashConfig does not own the data.
  if (mode == kCryptoJobAsync)
    tracker->TrackFieldWithSize("in", in.size());
}

Maybe<bool> HashTraits::EncodeOutput(
    Environment* env,
    const HashConfig& params,
    ByteSource* out,
    v8::Local<v8::Value>* result) {
  *result = out->ToArrayBuffer(env);
  return Just(!result->IsEmpty());
}

Maybe<bool> HashTraits::AdditionalConfig(
    CryptoJobMode mode,
    const FunctionCallbackInfo<Value>& args,
    unsigned int offset,
    HashConfig* params) {
  Environment* env = Environment::GetCurrent(args);

  params->mode = mode;

  CHECK(args[offset]->IsString());  // Hash algorithm
  Utf8Value digest(env->isolate(), args[offset]);
  params->digest = EVP_get_digestbyname(*digest);
  if (UNLIKELY(params->digest == nullptr)) {
    THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
    return Nothing<bool>();
  }

  ArrayBufferOrViewContents<char> data(args[offset + 1]);
  if (UNLIKELY(!data.CheckSizeInt32())) {
    THROW_ERR_OUT_OF_RANGE(env, "data is too big");
    return Nothing<bool>();
  }
  params->in = mode == kCryptoJobAsync
      ? data.ToCopy()
      : data.ToByteSource();

  unsigned int expected = EVP_MD_size(params->digest);
  params->length = expected;
  if (UNLIKELY(args[offset + 2]->IsUint32())) {
    // length is expressed in terms of bits
    params->length =
        static_cast<uint32_t>(args[offset + 2]
            .As<Uint32>()->Value()) / CHAR_BIT;
    if (params->length != expected) {
      if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) {
        THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
        return Nothing<bool>();
      }
    }
  }

  return Just(true);
}

bool HashTraits::DeriveBits(
    Environment* env,
    const HashConfig& params,
    ByteSource* out) {
  EVPMDCtxPointer ctx(EVP_MD_CTX_new());

  if (UNLIKELY(!ctx ||
               EVP_DigestInit_ex(ctx.get(), params.digest, nullptr) <= 0 ||
               EVP_DigestUpdate(
                   ctx.get(), params.in.data<char>(), params.in.size()) <= 0)) {
    return false;
  }

  if (LIKELY(params.length > 0)) {
    unsigned int length = params.length;
    ByteSource::Builder buf(length);

    size_t expected = EVP_MD_CTX_size(ctx.get());

    int ret =
        (length == expected)
            ? EVP_DigestFinal_ex(ctx.get(), buf.data<unsigned char>(), &length)
            : EVP_DigestFinalXOF(ctx.get(), buf.data<unsigned char>(), length);

    if (UNLIKELY(ret != 1))
      return false;

    *out = std::move(buf).release();
  }

  return true;
}

void InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK_EQ(args.Length(), 3);

  CHECK(args[0]->IsString());
  Utf8Value algorithm(env->isolate(), args[0]);

  CHECK(args[1]->IsString() || IsAnyBufferSource(args[1]));
  ByteSource content = ByteSource::FromStringOrBuffer(env, args[1]);

  CHECK(args[2]->IsArrayBufferView());
  ArrayBufferOrViewContents<unsigned char> expected(args[2]);

  const EVP_MD* md_type = EVP_get_digestbyname(*algorithm);
  unsigned char digest[EVP_MAX_MD_SIZE];
  unsigned int digest_size;
  if (md_type == nullptr || EVP_Digest(content.data(),
                                       content.size(),
                                       digest,
                                       &digest_size,
                                       md_type,
                                       nullptr) != 1) {
    return ThrowCryptoError(
        env, ERR_get_error(), "Digest method not supported");
  }

  if (digest_size != expected.size() ||
      CRYPTO_memcmp(digest, expected.data(), digest_size) != 0) {
    Local<Value> error;
    MaybeLocal<Value> rc =
        StringBytes::Encode(env->isolate(),
                            reinterpret_cast<const char*>(digest),
                            digest_size,
                            BASE64,
                            &error);
    if (rc.IsEmpty()) {
      CHECK(!error.IsEmpty());
      env->isolate()->ThrowException(error);
      return;
    }
    args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
  }
}
}  // namespace crypto
}  // namespace node

Zerion Mini Shell 1.0