Skip to content

Commit cff9d1d

Browse files
committed
fixup! crypto: use EVP_MD_fetch and cache EVP_MD for hashes
1 parent 90c6950 commit cff9d1d

File tree

3 files changed

+114
-42
lines changed

3 files changed

+114
-42
lines changed

lib/internal/crypto/hash.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
HashJob,
1212
Hmac: _Hmac,
1313
kCryptoJobAsync,
14+
getHashes,
1415
} = internalBinding('crypto');
1516

1617
const {
@@ -56,16 +57,50 @@ const LazyTransform = require('internal/streams/lazy_transform');
5657
const kState = Symbol('kState');
5758
const kFinalized = Symbol('kFinalized');
5859

60+
const {
61+
namespace: {
62+
isBuildingSnapshot,
63+
addSerializeCallback,
64+
addDeserializeCallback,
65+
},
66+
} = require('internal/v8/startup_snapshot');
67+
68+
let algorithmMap;
69+
function initializeAlgorithmMap() {
70+
if (algorithmMap === undefined) {
71+
algorithmMap = { __proto__: null };
72+
const hashes = getHashes();
73+
for (let i = 0; i < hashes.length; ++i) {
74+
algorithmMap[[hashes[i]]] = i;
75+
}
76+
if (isBuildingSnapshot()) {
77+
// For dynamic linking, clear the map.
78+
addSerializeCallback(() => { algorithmMap = undefined; });
79+
addDeserializeCallback(initializeAlgorithmMap);
80+
}
81+
}
82+
}
83+
84+
function getAlgorithmId(algorithm) {
85+
initializeAlgorithmMap();
86+
const result = algorithmMap[algorithm];
87+
return result === undefined ? -1 : result;
88+
}
89+
90+
initializeAlgorithmMap();
91+
5992
function Hash(algorithm, options) {
6093
if (!(this instanceof Hash))
6194
return new Hash(algorithm, options);
62-
if (!(algorithm instanceof _Hash))
95+
const isCopy = algorithm instanceof _Hash;
96+
if (!isCopy)
6397
validateString(algorithm, 'algorithm');
6498
const xofLen = typeof options === 'object' && options !== null ?
6599
options.outputLength : undefined;
66100
if (xofLen !== undefined)
67101
validateUint32(xofLen, 'options.outputLength');
68-
this[kHandle] = new _Hash(algorithm, xofLen);
102+
const algorithmId = isCopy ? -1 : getAlgorithmId(algorithm);
103+
this[kHandle] = new _Hash(algorithm, algorithmId, xofLen);
69104
this[kState] = {
70105
[kFinalized]: false,
71106
};

src/crypto/crypto_hash.cc

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ namespace node {
1414
using v8::Context;
1515
using v8::FunctionCallbackInfo;
1616
using v8::FunctionTemplate;
17+
using v8::Int32;
1718
using v8::Isolate;
1819
using v8::Just;
1920
using v8::Local;
2021
using v8::Maybe;
2122
using v8::MaybeLocal;
2223
using v8::Nothing;
2324
using v8::Object;
25+
using v8::String;
2426
using v8::Uint32;
2527
using v8::Value;
2628

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

37-
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
38-
Environment* env = Environment::GetCurrent(args);
39-
MarkPopErrorOnReturn mark_pop_error_on_return;
40-
CipherPushContext ctx(env);
41-
EVP_MD_do_all_sorted(
39+
void CacheSupportedHashAlgorithms(const EVP_MD* md,
40+
const char* from,
41+
const char* to,
42+
void* arg) {
43+
if (!from) return;
44+
45+
#if OPENSSL_VERSION_MAJOR >= 3
46+
const EVP_MD* implicit_md = EVP_get_digestbyname(from);
47+
if (!implicit_md) return;
48+
const char* real_name = EVP_MD_get0_name(implicit_md);
49+
if (!real_name) return;
50+
// EVP_*_fetch() does not support alias names, so we need to pass it the
51+
// real/original algorithm name.
52+
// We use EVP_*_fetch() as a filter here because it will only return an
53+
// instance if the algorithm is supported by the public OpenSSL APIs (some
54+
// algorithms are used internally by OpenSSL and are also passed to this
55+
// callback).
56+
EVP_MD* explicit_md = EVP_MD_fetch(nullptr, real_name, nullptr);
57+
if (!explicit_md) return;
58+
#endif // OPENSSL_VERSION_MAJOR >= 3
59+
60+
Environment* env = static_cast<Environment*>(arg);
61+
env->supported_hash_algorithms.push_back(from);
62+
4263
#if OPENSSL_VERSION_MAJOR >= 3
43-
array_push_back<EVP_MD,
44-
EVP_MD_fetch,
45-
EVP_MD_free,
46-
EVP_get_digestbyname,
47-
EVP_MD_get0_name>,
48-
#else
49-
array_push_back<EVP_MD>,
50-
#endif
51-
&ctx);
52-
args.GetReturnValue().Set(ctx.ToJSArray());
64+
env->evp_md_cache.emplace_back(explicit_md);
65+
#endif // OPENSSL_VERSION_MAJOR >= 3
5366
}
5467

55-
const EVP_MD* GetDigestImplementation(Environment* env,
56-
const Utf8Value& hash_type) {
68+
const std::vector<std::string>& GetSupportedHashAlgorithms(Environment* env) {
69+
if (env->supported_hash_algorithms.empty()) {
70+
MarkPopErrorOnReturn mark_pop_error_on_return;
71+
std::vector<std::string> results;
72+
EVP_MD_do_all_sorted(CacheSupportedHashAlgorithms, env);
5773
#if OPENSSL_VERSION_MAJOR >= 3
58-
std::string hash_type_str = hash_type.ToString();
59-
auto it = env->evp_md_cache.find(hash_type_str);
60-
if (it == env->evp_md_cache.end()) {
61-
EVP_MD* explicit_md = EVP_MD_fetch(nullptr, hash_type_str.c_str(), nullptr);
62-
if (explicit_md != nullptr) {
63-
env->evp_md_cache.emplace(hash_type_str, explicit_md);
64-
return explicit_md;
65-
} else {
66-
// We'll do a fallback.
67-
ERR_clear_error();
68-
}
69-
} else {
70-
return it->second.get();
71-
}
74+
CHECK_EQ(env->supported_hash_algorithms.size(), env->evp_md_cache.size());
75+
CHECK_GE(env->supported_hash_algorithms.size(), 0);
7276
#endif // OPENSSL_VERSION_MAJOR >= 3
73-
// EVP_MD_fetch failed, fallback to EVP_get_digestbyname.
74-
return EVP_get_digestbyname(*hash_type);
77+
}
78+
return env->supported_hash_algorithms;
79+
}
80+
81+
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
82+
Local<Context> context = args.GetIsolate()->GetCurrentContext();
83+
Environment* env = Environment::GetCurrent(context);
84+
const std::vector<std::string>& results = GetSupportedHashAlgorithms(env);
85+
86+
Local<Value> ret;
87+
if (ToV8Value(context, results).ToLocal(&ret)) {
88+
args.GetReturnValue().Set(ret);
89+
}
90+
}
91+
92+
const EVP_MD* GetDigestImplementation(Environment* env,
93+
Local<Value> algorithm,
94+
Local<Value> algorithm_id) {
95+
CHECK(algorithm->IsString());
96+
CHECK(algorithm_id->IsInt32());
97+
int32_t id = algorithm_id.As<Int32>()->Value();
98+
99+
const std::vector<std::string>& algorithms = GetSupportedHashAlgorithms(env);
100+
if (id != -1) {
101+
CHECK_LT(static_cast<size_t>(id), algorithms.size());
102+
auto& ptr = env->evp_md_cache[id];
103+
CHECK_NOT_NULL(ptr.get());
104+
return ptr.get();
105+
}
106+
107+
// It could be unsupported algorithms.
108+
std::string algorithm_str;
109+
Utf8Value utf8(env->isolate(), algorithm);
110+
algorithm_str = utf8.ToString();
111+
const EVP_MD* implicit_md = EVP_get_digestbyname(algorithm_str.c_str());
112+
return implicit_md;
75113
}
76114

77115
void Hash::Initialize(Environment* env, Local<Object> target) {
@@ -110,19 +148,17 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {
110148

111149
const Hash* orig = nullptr;
112150
const EVP_MD* md = nullptr;
113-
114151
if (args[0]->IsObject()) {
115152
ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
116153
md = EVP_MD_CTX_md(orig->mdctx_.get());
117154
} else {
118-
const Utf8Value hash_type(env->isolate(), args[0]);
119-
md = GetDigestImplementation(env, hash_type);
155+
md = GetDigestImplementation(env, args[0], args[1]);
120156
}
121157

122158
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
123-
if (!args[1]->IsUndefined()) {
124-
CHECK(args[1]->IsUint32());
125-
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
159+
if (!args[2]->IsUndefined()) {
160+
CHECK(args[2]->IsUint32());
161+
xof_md_len = Just<unsigned int>(args[2].As<Uint32>()->Value());
126162
}
127163

128164
Hash* hash = new Hash(env, args.This());

src/env.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,8 @@ class Environment : public MemoryRetainer {
10221022
#if OPENSSL_VERSION_MAJOR >= 3
10231023
// We declare another alias here to avoid having to include crypto_util.h
10241024
using EVPMDPointer = DeleteFnPtr<EVP_MD, EVP_MD_free>;
1025-
std::unordered_map<std::string, EVPMDPointer> evp_md_cache;
1025+
std::vector<EVPMDPointer> evp_md_cache;
1026+
std::vector<std::string> supported_hash_algorithms;
10261027
#endif // OPENSSL_VERSION_MAJOR
10271028

10281029
private:

0 commit comments

Comments
 (0)