GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: crypto/crypto_hash.cc Lines: 137 161 85.1 %
Date: 2022-06-08 04:15:48 Branches: 68 110 61.8 %

Line Branch Exec Source
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::FunctionCallbackInfo;
15
using v8::FunctionTemplate;
16
using v8::Just;
17
using v8::Local;
18
using v8::Maybe;
19
using v8::MaybeLocal;
20
using v8::Nothing;
21
using v8::Object;
22
using v8::Uint32;
23
using v8::Value;
24
25
namespace crypto {
26
1302
Hash::Hash(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
27
1302
  MakeWeak();
28
1302
}
29
30
void Hash::MemoryInfo(MemoryTracker* tracker) const {
31
  tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
32
  tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
33
}
34
35
4
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
36
4
  Environment* env = Environment::GetCurrent(args);
37
8
  MarkPopErrorOnReturn mark_pop_error_on_return;
38
4
  CipherPushContext ctx(env);
39
4
  EVP_MD_do_all_sorted(
40
#if OPENSSL_VERSION_MAJOR >= 3
41
    array_push_back<EVP_MD,
42
                    EVP_MD_fetch,
43
                    EVP_MD_free,
44
                    EVP_get_digestbyname,
45
                    EVP_MD_get0_name>,
46
#else
47
    array_push_back<EVP_MD>,
48
#endif
49
    &ctx);
50
8
  args.GetReturnValue().Set(ctx.ToJSArray());
51
4
}
52
53
853
void Hash::Initialize(Environment* env, Local<Object> target) {
54
853
  Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
55
56
1706
  t->InstanceTemplate()->SetInternalFieldCount(
57
      Hash::kInternalFieldCount);
58
853
  t->Inherit(BaseObject::GetConstructorTemplate(env));
59
60
853
  env->SetProtoMethod(t, "update", HashUpdate);
61
853
  env->SetProtoMethod(t, "digest", HashDigest);
62
63
853
  env->SetConstructorFunction(target, "Hash", t);
64
65
853
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
66
67
853
  HashJob::Initialize(env, target);
68
853
}
69
70
5206
void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
71
5206
  registry->Register(New);
72
5206
  registry->Register(HashUpdate);
73
5206
  registry->Register(HashDigest);
74
5206
  registry->Register(GetHashes);
75
76
5206
  HashJob::RegisterExternalReferences(registry);
77
5206
}
78
79
1302
void Hash::New(const FunctionCallbackInfo<Value>& args) {
80
1302
  Environment* env = Environment::GetCurrent(args);
81
82
1302
  const Hash* orig = nullptr;
83
1302
  const EVP_MD* md = nullptr;
84
85
1302
  if (args[0]->IsObject()) {
86
13
    ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
87
5
    md = EVP_MD_CTX_md(orig->mdctx_.get());
88
  } else {
89
2594
    const Utf8Value hash_type(env->isolate(), args[0]);
90
1297
    md = EVP_get_digestbyname(*hash_type);
91
  }
92
93
1302
  Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
94
2604
  if (!args[1]->IsUndefined()) {
95
13
    CHECK(args[1]->IsUint32());
96
26
    xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
97
  }
98
99
1302
  Hash* hash = new Hash(env, args.This());
100

1302
  if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
101
3
    return ThrowCryptoError(env, ERR_get_error(),
102
3
                            "Digest method not supported");
103
  }
104
105

1304
  if (orig != nullptr &&
106
5
      0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
107
    return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
108
  }
109
}
110
111
1300
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
112
1300
  mdctx_.reset(EVP_MD_CTX_new());
113

1300
  if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
114
    mdctx_.reset();
115
    return false;
116
  }
117
118
1300
  md_len_ = EVP_MD_size(md);
119

1313
  if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
120
    // This is a little hack to cause createHash to fail when an incorrect
121
    // hashSize option was passed for a non-XOF hash function.
122
12
    if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
123
1
      EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
124
1
      return false;
125
    }
126
11
    md_len_ = xof_md_len.FromJust();
127
  }
128
129
1299
  return true;
130
}
131
132
1536
bool Hash::HashUpdate(const char* data, size_t len) {
133
1536
  if (!mdctx_)
134
    return false;
135
1536
  return EVP_DigestUpdate(mdctx_.get(), data, len) == 1;
136
}
137
138
1536
void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
139
1536
  Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
140
1536
                        const char* data, size_t size) {
141
1536
    Environment* env = Environment::GetCurrent(args);
142
1536
    if (UNLIKELY(size > INT_MAX))
143
      return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
144
1536
    bool r = hash->HashUpdate(data, size);
145
3072
    args.GetReturnValue().Set(r);
146
  });
147
1536
}
148
149
1236
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
150
1236
  Environment* env = Environment::GetCurrent(args);
151
152
  Hash* hash;
153
1236
  ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
154
155
1236
  enum encoding encoding = BUFFER;
156
1236
  if (args.Length() >= 1) {
157
1224
    encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
158
  }
159
160
1236
  unsigned int len = hash->md_len_;
161
162
  // TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
163
  // platforms and will cause a segmentation fault if called. This workaround
164
  // causes hash.digest() to correctly return an empty buffer / string.
165
  // See https://github.com/openssl/openssl/issues/9431.
166
167

1236
  if (!hash->digest_ && len > 0) {
168
    // Some hash algorithms such as SHA3 do not support calling
169
    // EVP_DigestFinal_ex more than once, however, Hash._flush
170
    // and Hash.digest can both be used to retrieve the digest,
171
    // so we need to cache it.
172
    // See https://github.com/nodejs/node/issues/28245.
173
174
1233
    char* md_value = MallocOpenSSL<char>(len);
175
1233
    ByteSource digest = ByteSource::Allocated(md_value, len);
176
177
1233
    size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
178
    int ret;
179
1233
    if (len == default_len) {
180
1227
      ret = EVP_DigestFinal_ex(
181
1227
          hash->mdctx_.get(),
182
          reinterpret_cast<unsigned char*>(md_value),
183
          &len);
184
      // The output length should always equal hash->md_len_
185
1227
      CHECK_EQ(len, hash->md_len_);
186
    } else {
187
6
      ret = EVP_DigestFinalXOF(
188
6
          hash->mdctx_.get(),
189
          reinterpret_cast<unsigned char*>(md_value),
190
          len);
191
    }
192
193
1233
    if (ret != 1)
194
      return ThrowCryptoError(env, ERR_get_error());
195
196
1233
    hash->digest_ = std::move(digest);
197
  }
198
199
  Local<Value> error;
200
  MaybeLocal<Value> rc =
201
      StringBytes::Encode(env->isolate(),
202
1236
                          hash->digest_.get(),
203
                          len,
204
                          encoding,
205
1236
                          &error);
206
1236
  if (rc.IsEmpty()) {
207
    CHECK(!error.IsEmpty());
208
    env->isolate()->ThrowException(error);
209
    return;
210
  }
211
3708
  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
212
}
213
214
141
HashConfig::HashConfig(HashConfig&& other) noexcept
215
141
    : mode(other.mode),
216
141
      in(std::move(other.in)),
217
141
      digest(other.digest),
218
141
      length(other.length) {}
219
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
226
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
232
141
Maybe<bool> HashTraits::EncodeOutput(
233
    Environment* env,
234
    const HashConfig& params,
235
    ByteSource* out,
236
    v8::Local<v8::Value>* result) {
237
282
  *result = out->ToArrayBuffer(env);
238
141
  return Just(!result->IsEmpty());
239
}
240
241
154
Maybe<bool> HashTraits::AdditionalConfig(
242
    CryptoJobMode mode,
243
    const FunctionCallbackInfo<Value>& args,
244
    unsigned int offset,
245
    HashConfig* params) {
246
154
  Environment* env = Environment::GetCurrent(args);
247
248
154
  params->mode = mode;
249
250

462
  CHECK(args[offset]->IsString());  // Hash algorithm
251
462
  Utf8Value digest(env->isolate(), args[offset]);
252
154
  params->digest = EVP_get_digestbyname(*digest);
253
154
  if (UNLIKELY(params->digest == nullptr)) {
254
12
    THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
255
12
    return Nothing<bool>();
256
  }
257
258
426
  ArrayBufferOrViewContents<char> data(args[offset + 1]);
259
142
  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
284
      ? data.ToCopy()
265
142
      : data.ToByteSource();
266
267
142
  unsigned int expected = EVP_MD_size(params->digest);
268
142
  params->length = expected;
269

284
  if (UNLIKELY(args[offset + 2]->IsUint32())) {
270
    // length is expressed in terms of bits
271
5
    params->length =
272
10
        static_cast<uint32_t>(args[offset + 2]
273
5
            .As<Uint32>()->Value()) / CHAR_BIT;
274
5
    if (params->length != expected) {
275
1
      if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) {
276
1
        THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
277
1
        return Nothing<bool>();
278
      }
279
    }
280
  }
281
282
141
  return Just(true);
283
}
284
285
141
bool HashTraits::DeriveBits(
286
    Environment* env,
287
    const HashConfig& params,
288
    ByteSource* out) {
289
282
  EVPMDPointer ctx(EVP_MD_CTX_new());
290
291
423
  if (UNLIKELY(!ctx ||
292
               EVP_DigestInit_ex(ctx.get(), params.digest, nullptr) <= 0 ||
293
               EVP_DigestUpdate(
294
                   ctx.get(),
295
                   params.in.get(),
296


423
                   params.in.size()) <= 0)) {
297
    return false;
298
  }
299
300
141
  if (LIKELY(params.length > 0)) {
301
141
    unsigned int length = params.length;
302
141
    char* data = MallocOpenSSL<char>(length);
303
141
    ByteSource buf = ByteSource::Allocated(data, length);
304
141
    unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
305
306
141
    size_t expected = EVP_MD_CTX_size(ctx.get());
307
308
141
    int ret = (length == expected)
309
141
        ? EVP_DigestFinal_ex(ctx.get(), ptr, &length)
310
        : EVP_DigestFinalXOF(ctx.get(), ptr, length);
311
312
141
    if (UNLIKELY(ret != 1))
313
      return false;
314
315
141
    *out = std::move(buf);
316
  }
317
318
141
  return true;
319
}
320
321
}  // namespace crypto
322
}  // namespace node