@@ -14,11 +14,13 @@ namespace node {
1414using v8::Context;
1515using v8::FunctionCallbackInfo;
1616using v8::FunctionTemplate;
17+ using v8::Int32;
1718using v8::Isolate;
1819using v8::Just;
1920using v8::Local;
2021using v8::Maybe;
2122using v8::MaybeLocal;
23+ using v8::Name;
2224using v8::Nothing;
2325using v8::Object;
2426using v8::Uint32;
@@ -34,22 +36,170 @@ void Hash::MemoryInfo(MemoryTracker* tracker) const {
3436 tracker->TrackFieldWithSize (" md" , digest_ ? md_len_ : 0 );
3537}
3638
39+ #if OPENSSL_VERSION_MAJOR >= 3
40+ void PushAliases (const char * name, void * data) {
41+ static_cast <std::vector<std::string>*>(data)->push_back (name);
42+ }
43+
44+ EVP_MD* GetCachedMDByID (Environment* env, size_t id) {
45+ CHECK_LT (id, env->evp_md_cache .size ());
46+ EVP_MD* result = env->evp_md_cache [id].get ();
47+ CHECK_NOT_NULL (result);
48+ return result;
49+ }
50+
51+ struct MaybeCachedMD {
52+ EVP_MD* explicit_md = nullptr ;
53+ const EVP_MD* implicit_md = nullptr ;
54+ int32_t cache_id = -1 ;
55+ };
56+
57+ MaybeCachedMD FetchAndMaybeCacheMD (Environment* env, const char * search_name) {
58+ const EVP_MD* implicit_md = EVP_get_digestbyname (search_name);
59+ if (!implicit_md) return {nullptr , nullptr , -1 };
60+
61+ const char * real_name = EVP_MD_get0_name (implicit_md);
62+ if (!real_name) return {nullptr , implicit_md, -1 };
63+
64+ auto it = env->alias_to_md_id_map .find (real_name);
65+ if (it != env->alias_to_md_id_map .end ()) {
66+ size_t id = it->second ;
67+ return {GetCachedMDByID (env, id), implicit_md, static_cast <int32_t >(id)};
68+ }
69+
70+ // EVP_*_fetch() does not support alias names, so we need to pass it the
71+ // real/original algorithm name.
72+ // We use EVP_*_fetch() as a filter here because it will only return an
73+ // instance if the algorithm is supported by the public OpenSSL APIs (some
74+ // algorithms are used internally by OpenSSL and are also passed to this
75+ // callback).
76+ EVP_MD* explicit_md = EVP_MD_fetch (nullptr , real_name, nullptr );
77+ if (!explicit_md) return {nullptr , implicit_md, -1 };
78+
79+ // Cache the EVP_MD* fetched.
80+ env->evp_md_cache .emplace_back (explicit_md);
81+ size_t id = env->evp_md_cache .size () - 1 ;
82+
83+ // Add all the aliases to the map to speed up next lookup.
84+ std::vector<std::string> aliases;
85+ EVP_MD_names_do_all (explicit_md, PushAliases, &aliases);
86+ for (const auto & alias : aliases) {
87+ env->alias_to_md_id_map .emplace (alias, id);
88+ }
89+ env->alias_to_md_id_map .emplace (search_name, id);
90+
91+ return {explicit_md, implicit_md, static_cast <int32_t >(id)};
92+ }
93+
94+ void SaveSupportedHashAlgorithmsAndCacheMD (const EVP_MD* md,
95+ const char * from,
96+ const char * to,
97+ void * arg) {
98+ if (!from) return ;
99+ Environment* env = static_cast <Environment*>(arg);
100+ auto result = FetchAndMaybeCacheMD (env, from);
101+ if (result.explicit_md ) {
102+ env->supported_hash_algorithms .push_back (from);
103+ }
104+ }
105+
106+ #else
107+ void SaveSupportedHashAlgorithms (const EVP_MD* md,
108+ const char * from,
109+ const char * to,
110+ void * arg) {
111+ if (!from) return ;
112+ Environment* env = static_cast <Environment*>(arg);
113+ env->supported_hash_algorithms .push_back (from);
114+ }
115+ #endif // OPENSSL_VERSION_MAJOR >= 3
116+
117+ const std::vector<std::string>& GetSupportedHashAlgorithms (Environment* env) {
118+ if (env->supported_hash_algorithms .empty ()) {
119+ MarkPopErrorOnReturn mark_pop_error_on_return;
120+ #if OPENSSL_VERSION_MAJOR >= 3
121+ // Since we'll fetch the EVP_MD*, cache them along the way to speed up
122+ // later lookups instead of throwing them away immediately.
123+ EVP_MD_do_all_sorted (SaveSupportedHashAlgorithmsAndCacheMD, env);
124+ #else
125+ EVP_MD_do_all_sorted (SaveSupportedHashAlgorithms, env);
126+ #endif
127+ }
128+ return env->supported_hash_algorithms ;
129+ }
130+
37131void 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 (
132+ Local<Context> context = args.GetIsolate ()->GetCurrentContext ();
133+ Environment* env = Environment::GetCurrent (context);
134+ const std::vector<std::string>& results = GetSupportedHashAlgorithms (env);
135+
136+ Local<Value> ret;
137+ if (ToV8Value (context, results).ToLocal (&ret)) {
138+ args.GetReturnValue ().Set (ret);
139+ }
140+ }
141+
142+ void Hash::GetCachedAliases (const FunctionCallbackInfo<Value>& args) {
143+ Isolate* isolate = args.GetIsolate ();
144+ Local<Context> context = args.GetIsolate ()->GetCurrentContext ();
145+ Environment* env = Environment::GetCurrent (context);
146+ std::vector<Local<Name>> names;
147+ std::vector<Local<Value>> values;
148+ size_t size = env->alias_to_md_id_map .size ();
42149#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>,
150+ names.reserve (size);
151+ values.reserve (size);
152+ for (auto & [alias, id] : env->alias_to_md_id_map ) {
153+ names.push_back (OneByteString (isolate, alias.c_str (), alias.size ()));
154+ values.push_back (v8::Uint32::New (isolate, id));
155+ }
48156#else
49- array_push_back<EVP_MD>,
157+ CHECK (env->alias_to_md_id_map .empty ());
158+ #endif
159+ Local<Value> prototype = v8::Null (isolate);
160+ Local<Object> result =
161+ Object::New (isolate, prototype, names.data (), values.data (), size);
162+ args.GetReturnValue ().Set (result);
163+ }
164+
165+ const EVP_MD* GetDigestImplementation (Environment* env,
166+ Local<Value> algorithm,
167+ Local<Value> cache_id_val,
168+ Local<Value> algorithm_cache) {
169+ CHECK (algorithm->IsString ());
170+ CHECK (cache_id_val->IsInt32 ());
171+ CHECK (algorithm_cache->IsObject ());
172+
173+ #if OPENSSL_VERSION_MAJOR >= 3
174+ int32_t cache_id = cache_id_val.As <Int32>()->Value ();
175+ if (cache_id != -1 ) { // Alias already cached, return the cached EVP_MD*.
176+ return GetCachedMDByID (env, cache_id);
177+ }
178+
179+ // Only decode the algorithm when we don't have it cached to avoid
180+ // unnecessary overhead.
181+ Isolate* isolate = env->isolate ();
182+ Utf8Value utf8 (isolate, algorithm);
183+
184+ auto result = FetchAndMaybeCacheMD (env, *utf8);
185+ if (result.cache_id != -1 ) {
186+ // Add the alias to both C++ side and JS side to speedup the lookup
187+ // next time.
188+ env->alias_to_md_id_map .emplace (*utf8, result.cache_id );
189+ if (algorithm_cache.As <Object>()
190+ ->Set (isolate->GetCurrentContext (),
191+ algorithm,
192+ v8::Int32::New (isolate, result.cache_id ))
193+ .IsNothing ()) {
194+ return nullptr ;
195+ }
196+ }
197+
198+ return result.explicit_md ? result.explicit_md : result.implicit_md ;
199+ #else
200+ Utf8Value utf8 (env->isolate (), algorithm);
201+ return EVP_get_digestbyname (*utf8);
50202#endif
51- &ctx);
52- args.GetReturnValue ().Set (ctx.ToJSArray ());
53203}
54204
55205void Hash::Initialize (Environment* env, Local<Object> target) {
@@ -65,6 +215,7 @@ void Hash::Initialize(Environment* env, Local<Object> target) {
65215 SetConstructorFunction (context, target, " Hash" , t);
66216
67217 SetMethodNoSideEffect (context, target, " getHashes" , GetHashes);
218+ SetMethodNoSideEffect (context, target, " getCachedAliases" , GetCachedAliases);
68219
69220 HashJob::Initialize (env, target);
70221
@@ -77,24 +228,24 @@ void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
77228 registry->Register (HashUpdate);
78229 registry->Register (HashDigest);
79230 registry->Register (GetHashes);
231+ registry->Register (GetCachedAliases);
80232
81233 HashJob::RegisterExternalReferences (registry);
82234
83235 registry->Register (InternalVerifyIntegrity);
84236}
85237
238+ // new Hash(algorithm, algorithmId, xofLen, algorithmCache)
86239void Hash::New (const FunctionCallbackInfo<Value>& args) {
87240 Environment* env = Environment::GetCurrent (args);
88241
89242 const Hash* orig = nullptr ;
90243 const EVP_MD* md = nullptr ;
91-
92244 if (args[0 ]->IsObject ()) {
93245 ASSIGN_OR_RETURN_UNWRAP (&orig, args[0 ].As <Object>());
94246 md = EVP_MD_CTX_md (orig->mdctx_ .get ());
95247 } else {
96- const Utf8Value hash_type (env->isolate (), args[0 ]);
97- md = EVP_get_digestbyname (*hash_type);
248+ md = GetDigestImplementation (env, args[0 ], args[2 ], args[3 ]);
98249 }
99250
100251 Maybe<unsigned int > xof_md_len = Nothing<unsigned int >();
@@ -284,7 +435,7 @@ bool HashTraits::DeriveBits(
284435 Environment* env,
285436 const HashConfig& params,
286437 ByteSource* out) {
287- EVPMDPointer ctx (EVP_MD_CTX_new ());
438+ EVPMDCtxPointer ctx (EVP_MD_CTX_new ());
288439
289440 if (UNLIKELY (!ctx ||
290441 EVP_DigestInit_ex (ctx.get (), params.digest , nullptr ) <= 0 ||
@@ -357,6 +508,5 @@ void InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value>& args) {
357508 args.GetReturnValue ().Set (rc.FromMaybe (Local<Value>()));
358509 }
359510}
360-
361511} // namespace crypto
362512} // namespace node
0 commit comments