1 #include "crypto/crypto_hash.h"
2 #include "async_wrap-inl.h"
3 #include "base_object-inl.h"
4 #include "env-inl.h"
5 #include "memory_tracker-inl.h"
6 #include "string_bytes.h"
7 #include "threadpoolwork-inl.h"
8 #include "v8.h"
9
10 #include <cstdio>
11
12 namespace node {
13
14 using v8::Context;
15 using v8::FunctionCallbackInfo;
16 using v8::FunctionTemplate;
17 using v8::Isolate;
18 using v8::Just;
19 using v8::Local;
20 using v8::Maybe;
21 using v8::MaybeLocal;
22 using v8::Nothing;
23 using v8::Object;
24 using v8::Uint32;
25 using v8::Value;
26
27 namespace crypto {
Hash(Environment * env,Local<Object> wrap)28 Hash::Hash(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
29 MakeWeak();
30 }
31
MemoryInfo(MemoryTracker * tracker) const32 void Hash::MemoryInfo(MemoryTracker* tracker) const {
33 tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
34 tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
35 }
36
GetHashes(const FunctionCallbackInfo<Value> & args)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(
42 #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());
53 }
54
Initialize(Environment * env,Local<Object> target)55 void Hash::Initialize(Environment* env, Local<Object> target) {
56 Isolate* isolate = env->isolate();
57 Local<Context> context = env->context();
58 Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
59
60 t->InstanceTemplate()->SetInternalFieldCount(
61 Hash::kInternalFieldCount);
62 t->Inherit(BaseObject::GetConstructorTemplate(env));
63
64 SetProtoMethod(isolate, t, "update", HashUpdate);
65 SetProtoMethod(isolate, t, "digest", HashDigest);
66
67 SetConstructorFunction(context, target, "Hash", t);
68
69 SetMethodNoSideEffect(context, target, "getHashes", GetHashes);
70
71 HashJob::Initialize(env, target);
72
73 SetMethodNoSideEffect(
74 context, target, "internalVerifyIntegrity", InternalVerifyIntegrity);
75 }
76
RegisterExternalReferences(ExternalReferenceRegistry * registry)77 void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
78 registry->Register(New);
79 registry->Register(HashUpdate);
80 registry->Register(HashDigest);
81 registry->Register(GetHashes);
82
83 HashJob::RegisterExternalReferences(registry);
84
85 registry->Register(InternalVerifyIntegrity);
86 }
87
New(const FunctionCallbackInfo<Value> & args)88 void Hash::New(const FunctionCallbackInfo<Value>& args) {
89 Environment* env = Environment::GetCurrent(args);
90
91 const Hash* orig = nullptr;
92 const EVP_MD* md = nullptr;
93
94 if (args[0]->IsObject()) {
95 ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
96 md = EVP_MD_CTX_md(orig->mdctx_.get());
97 } else {
98 const Utf8Value hash_type(env->isolate(), args[0]);
99 md = EVP_get_digestbyname(*hash_type);
100 }
101
102 Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
103 if (!args[1]->IsUndefined()) {
104 CHECK(args[1]->IsUint32());
105 xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
106 }
107
108 Hash* hash = new Hash(env, args.This());
109 if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
110 return ThrowCryptoError(env, ERR_get_error(),
111 "Digest method not supported");
112 }
113
114 if (orig != nullptr &&
115 0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
116 return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
117 }
118 }
119
HashInit(const EVP_MD * md,Maybe<unsigned int> xof_md_len)120 bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
121 mdctx_.reset(EVP_MD_CTX_new());
122 if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
123 mdctx_.reset();
124 return false;
125 }
126
127 md_len_ = EVP_MD_size(md);
128 if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
129 // This is a little hack to cause createHash to fail when an incorrect
130 // hashSize option was passed for a non-XOF hash function.
131 if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
132 EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
133 return false;
134 }
135 md_len_ = xof_md_len.FromJust();
136 }
137
138 return true;
139 }
140
HashUpdate(const char * data,size_t len)141 bool Hash::HashUpdate(const char* data, size_t len) {
142 if (!mdctx_)
143 return false;
144 return EVP_DigestUpdate(mdctx_.get(), data, len) == 1;
145 }
146
HashUpdate(const FunctionCallbackInfo<Value> & args)147 void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
148 Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
149 const char* data, size_t size) {
150 Environment* env = Environment::GetCurrent(args);
151 if (UNLIKELY(size > INT_MAX))
152 return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
153 bool r = hash->HashUpdate(data, size);
154 args.GetReturnValue().Set(r);
155 });
156 }
157
HashDigest(const FunctionCallbackInfo<Value> & args)158 void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
159 Environment* env = Environment::GetCurrent(args);
160
161 Hash* hash;
162 ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
163
164 enum encoding encoding = BUFFER;
165 if (args.Length() >= 1) {
166 encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
167 }
168
169 unsigned int len = hash->md_len_;
170
171 // TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
172 // platforms and will cause a segmentation fault if called. This workaround
173 // causes hash.digest() to correctly return an empty buffer / string.
174 // See https://github.com/openssl/openssl/issues/9431.
175
176 if (!hash->digest_ && len > 0) {
177 // Some hash algorithms such as SHA3 do not support calling
178 // EVP_DigestFinal_ex more than once, however, Hash._flush
179 // and Hash.digest can both be used to retrieve the digest,
180 // so we need to cache it.
181 // See https://github.com/nodejs/node/issues/28245.
182
183 ByteSource::Builder digest(len);
184
185 size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
186 int ret;
187 if (len == default_len) {
188 ret = EVP_DigestFinal_ex(
189 hash->mdctx_.get(), digest.data<unsigned char>(), &len);
190 // The output length should always equal hash->md_len_
191 CHECK_EQ(len, hash->md_len_);
192 } else {
193 ret = EVP_DigestFinalXOF(
194 hash->mdctx_.get(), digest.data<unsigned char>(), len);
195 }
196
197 if (ret != 1)
198 return ThrowCryptoError(env, ERR_get_error());
199
200 hash->digest_ = std::move(digest).release();
201 }
202
203 Local<Value> error;
204 MaybeLocal<Value> rc = StringBytes::Encode(
205 env->isolate(), hash->digest_.data<char>(), len, encoding, &error);
206 if (rc.IsEmpty()) {
207 CHECK(!error.IsEmpty());
208 env->isolate()->ThrowException(error);
209 return;
210 }
211 args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
212 }
213
HashConfig(HashConfig && other)214 HashConfig::HashConfig(HashConfig&& other) noexcept
215 : mode(other.mode),
216 in(std::move(other.in)),
217 digest(other.digest),
218 length(other.length) {}
219
operator =(HashConfig && other)220 HashConfig& HashConfig::operator=(HashConfig&& other) noexcept {
221 if (&other == this) return *this;
222 this->~HashConfig();
223 return *new (this) HashConfig(std::move(other));
224 }
225
MemoryInfo(MemoryTracker * tracker) const226 void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
227 // If the Job is sync, then the HashConfig does not own the data.
228 if (mode == kCryptoJobAsync)
229 tracker->TrackFieldWithSize("in", in.size());
230 }
231
EncodeOutput(Environment * env,const HashConfig & params,ByteSource * out,v8::Local<v8::Value> * result)232 Maybe<bool> HashTraits::EncodeOutput(
233 Environment* env,
234 const HashConfig& params,
235 ByteSource* out,
236 v8::Local<v8::Value>* result) {
237 *result = out->ToArrayBuffer(env);
238 return Just(!result->IsEmpty());
239 }
240
AdditionalConfig(CryptoJobMode mode,const FunctionCallbackInfo<Value> & args,unsigned int offset,HashConfig * params)241 Maybe<bool> HashTraits::AdditionalConfig(
242 CryptoJobMode mode,
243 const FunctionCallbackInfo<Value>& args,
244 unsigned int offset,
245 HashConfig* params) {
246 Environment* env = Environment::GetCurrent(args);
247
248 params->mode = mode;
249
250 CHECK(args[offset]->IsString()); // Hash algorithm
251 Utf8Value digest(env->isolate(), args[offset]);
252 params->digest = EVP_get_digestbyname(*digest);
253 if (UNLIKELY(params->digest == nullptr)) {
254 THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
255 return Nothing<bool>();
256 }
257
258 ArrayBufferOrViewContents<char> data(args[offset + 1]);
259 if (UNLIKELY(!data.CheckSizeInt32())) {
260 THROW_ERR_OUT_OF_RANGE(env, "data is too big");
261 return Nothing<bool>();
262 }
263 params->in = mode == kCryptoJobAsync
264 ? data.ToCopy()
265 : data.ToByteSource();
266
267 unsigned int expected = EVP_MD_size(params->digest);
268 params->length = expected;
269 if (UNLIKELY(args[offset + 2]->IsUint32())) {
270 // length is expressed in terms of bits
271 params->length =
272 static_cast<uint32_t>(args[offset + 2]
273 .As<Uint32>()->Value()) / CHAR_BIT;
274 if (params->length != expected) {
275 if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) {
276 THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
277 return Nothing<bool>();
278 }
279 }
280 }
281
282 return Just(true);
283 }
284
DeriveBits(Environment * env,const HashConfig & params,ByteSource * out)285 bool HashTraits::DeriveBits(
286 Environment* env,
287 const HashConfig& params,
288 ByteSource* out) {
289 EVPMDPointer ctx(EVP_MD_CTX_new());
290
291 if (UNLIKELY(!ctx ||
292 EVP_DigestInit_ex(ctx.get(), params.digest, nullptr) <= 0 ||
293 EVP_DigestUpdate(
294 ctx.get(), params.in.data<char>(), params.in.size()) <= 0)) {
295 return false;
296 }
297
298 if (LIKELY(params.length > 0)) {
299 unsigned int length = params.length;
300 ByteSource::Builder buf(length);
301
302 size_t expected = EVP_MD_CTX_size(ctx.get());
303
304 int ret =
305 (length == expected)
306 ? EVP_DigestFinal_ex(ctx.get(), buf.data<unsigned char>(), &length)
307 : EVP_DigestFinalXOF(ctx.get(), buf.data<unsigned char>(), length);
308
309 if (UNLIKELY(ret != 1))
310 return false;
311
312 *out = std::move(buf).release();
313 }
314
315 return true;
316 }
317
InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value> & args)318 void InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value>& args) {
319 Environment* env = Environment::GetCurrent(args);
320
321 CHECK_EQ(args.Length(), 3);
322
323 CHECK(args[0]->IsString());
324 Utf8Value algorithm(env->isolate(), args[0]);
325
326 CHECK(args[1]->IsString() || IsAnyByteSource(args[1]));
327 ByteSource content = ByteSource::FromStringOrBuffer(env, args[1]);
328
329 CHECK(args[2]->IsArrayBufferView());
330 ArrayBufferOrViewContents<unsigned char> expected(args[2]);
331
332 const EVP_MD* md_type = EVP_get_digestbyname(*algorithm);
333 unsigned char digest[EVP_MAX_MD_SIZE];
334 unsigned int digest_size;
335 if (md_type == nullptr || EVP_Digest(content.data(),
336 content.size(),
337 digest,
338 &digest_size,
339 md_type,
340 nullptr) != 1) {
341 return ThrowCryptoError(
342 env, ERR_get_error(), "Digest method not supported");
343 }
344
345 if (digest_size != expected.size() ||
346 CRYPTO_memcmp(digest, expected.data(), digest_size) != 0) {
347 Local<Value> error;
348 MaybeLocal<Value> rc =
349 StringBytes::Encode(env->isolate(),
350 reinterpret_cast<const char*>(digest),
351 digest_size,
352 BASE64,
353 &error);
354 if (rc.IsEmpty()) {
355 CHECK(!error.IsEmpty());
356 env->isolate()->ThrowException(error);
357 return;
358 }
359 args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
360 }
361 }
362
363 } // namespace crypto
364 } // namespace node
365