Skip to content

Commit

Permalink
fixup! crypto: use EVP_MD_fetch and cache EVP_MD for hashes
Browse files Browse the repository at this point in the history
  • Loading branch information
joyeecheung committed Dec 4, 2023
1 parent 90c6950 commit 6dc4c98
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 42 deletions.
38 changes: 36 additions & 2 deletions lib/internal/crypto/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
HashJob,
Hmac: _Hmac,
kCryptoJobAsync,
getHashes,
} = internalBinding('crypto');

const {
Expand Down Expand Up @@ -56,16 +57,49 @@ const LazyTransform = require('internal/streams/lazy_transform');
const kState = Symbol('kState');
const kFinalized = Symbol('kFinalized');

const {
namespace: {
isBuildingSnapshot,
addSerializeCallback,
addDeserializeCallback

Check failure on line 64 in lib/internal/crypto/hash.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing trailing comma
},
} = require('internal/v8/startup_snapshot');

let algorithmMap;
function initializeAlgorithmMap() {
if (algorithmMap === undefined) {
algorithmMap = { __proto__: null };
const hashes = getHashes();
for (let i = 0; i < hashes.length; ++i) {
algorithmMap[[hashes[i]]] = i;
}
if (isBuildingSnapshot()) {
// For dynamic linking, clear the map.
addSerializeCallback(() => { algorithmMap = undefined; });
addDeserializeCallback(initializeAlgorithmMap);
}
}
}
function getAlgorithmId(algorithm) {

Check failure on line 83 in lib/internal/crypto/hash.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Expected blank line before this statement
initializeAlgorithmMap();
const result = algorithmMap[algorithm];
return result === undefined ? -1 : result;
}

initializeAlgorithmMap();

function Hash(algorithm, options) {
if (!(this instanceof Hash))
return new Hash(algorithm, options);
if (!(algorithm instanceof _Hash))
const isCopy = algorithm instanceof _Hash;
if (!isCopy)
validateString(algorithm, 'algorithm');
const xofLen = typeof options === 'object' && options !== null ?
options.outputLength : undefined;
if (xofLen !== undefined)
validateUint32(xofLen, 'options.outputLength');
this[kHandle] = new _Hash(algorithm, xofLen);
const algorithmId = isCopy ? -1 : getAlgorithmId(algorithm);
this[kHandle] = new _Hash(algorithm, algorithmId, xofLen);
this[kState] = {
[kFinalized]: false,
};
Expand Down
114 changes: 75 additions & 39 deletions src/crypto/crypto_hash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ 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::Nothing;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;

Expand All @@ -34,44 +36,80 @@ void Hash::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
}

void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
MarkPopErrorOnReturn mark_pop_error_on_return;
CipherPushContext ctx(env);
EVP_MD_do_all_sorted(
void CacheSupportedHashAlgorithms(const EVP_MD* md,
const char* from,
const char* to,
void* arg) {
if (!from) return;

#if OPENSSL_VERSION_MAJOR >= 3
const EVP_MD* implicit_md = EVP_get_digestbyname(from);
if (!implicit_md) return;
const char* real_name = EVP_MD_get0_name(implicit_md);
if (!real_name) return;
// 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;
#endif // OPENSSL_VERSION_MAJOR >= 3

Environment* env = static_cast<Environment*>(arg);
env->supported_hash_algorithms.push_back(from);

#if OPENSSL_VERSION_MAJOR >= 3
array_push_back<EVP_MD,
EVP_MD_fetch,
EVP_MD_free,
EVP_get_digestbyname,
EVP_MD_get0_name>,
#else
array_push_back<EVP_MD>,
#endif
&ctx);
args.GetReturnValue().Set(ctx.ToJSArray());
env->evp_md_cache.emplace_back(explicit_md);
#endif // OPENSSL_VERSION_MAJOR >= 3
}

const EVP_MD* GetDigestImplementation(Environment* env,
const Utf8Value& hash_type) {
const std::vector<std::string>& GetSupportedHashAlgorithms(Environment* env) {
if (env->supported_hash_algorithms.empty()) {
MarkPopErrorOnReturn mark_pop_error_on_return;
std::vector<std::string> results;
EVP_MD_do_all_sorted(CacheSupportedHashAlgorithms, env);
#if OPENSSL_VERSION_MAJOR >= 3
std::string hash_type_str = hash_type.ToString();
auto it = env->evp_md_cache.find(hash_type_str);
if (it == env->evp_md_cache.end()) {
EVP_MD* explicit_md = EVP_MD_fetch(nullptr, hash_type_str.c_str(), nullptr);
if (explicit_md != nullptr) {
env->evp_md_cache.emplace(hash_type_str, explicit_md);
return explicit_md;
} else {
// We'll do a fallback.
ERR_clear_error();
}
} else {
return it->second.get();
}
CHECK_EQ(env->supported_hash_algorithms.size(), env->evp_md_cache.size());
CHECK_GE(env->supported_hash_algorithms.size(), 0);
#endif // OPENSSL_VERSION_MAJOR >= 3
// EVP_MD_fetch failed, fallback to EVP_get_digestbyname.
return EVP_get_digestbyname(*hash_type);
}
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);
}
}

const EVP_MD* GetDigestImplementation(Environment* env,
Local<Value> algorithm,
Local<Value> algorithm_id) {
CHECK(algorithm->IsString());
CHECK(algorithm_id->IsInt32());
int32_t id = algorithm_id.As<Int32>()->Value();

const std::vector<std::string>& algorithms = GetSupportedHashAlgorithms(env);
if (id != -1) {
CHECK_LT(static_cast<size_t>(id), algorithms.size());
auto& ptr = env->evp_md_cache[id];
CHECK_NOT_NULL(ptr.get());
return ptr.get();
}

// It could be unsupported algorithms.
std::string algorithm_str;
Utf8Value utf8(env->isolate(), algorithm);
algorithm_str = utf8.ToString();
const EVP_MD* implicit_md = EVP_get_digestbyname(algorithm_str.c_str());
return implicit_md;
}

void Hash::Initialize(Environment* env, Local<Object> target) {
Expand Down Expand Up @@ -110,19 +148,17 @@ void Hash::New(const FunctionCallbackInfo<Value>& 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 {
const Utf8Value hash_type(env->isolate(), args[0]);
md = GetDigestImplementation(env, hash_type);
md = GetDigestImplementation(env, args[0], args[1]);
}

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());
if (!args[2]->IsUndefined()) {
CHECK(args[2]->IsUint32());
xof_md_len = Just<unsigned int>(args[2].As<Uint32>()->Value());
}

Hash* hash = new Hash(env, args.This());
Expand Down
3 changes: 2 additions & 1 deletion src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,8 @@ class Environment : public MemoryRetainer {
#if OPENSSL_VERSION_MAJOR >= 3
// We declare another alias here to avoid having to include crypto_util.h
using EVPMDPointer = DeleteFnPtr<EVP_MD, EVP_MD_free>;
std::unordered_map<std::string, EVPMDPointer> evp_md_cache;
std::vector<EVPMDPointer> evp_md_cache;
std::vector<std::string> supported_hash_algorithms;
#endif // OPENSSL_VERSION_MAJOR

private:
Expand Down

0 comments on commit 6dc4c98

Please sign in to comment.