• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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