%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/ |
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