GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: crypto/crypto_hash.cc Lines: 136 160 85.0 %
Date: 2022-02-05 04:14:12 Branches: 68 110 61.8 %

Line Branch Exec Source
1
#include "crypto/crypto_hash.h"
2
#include "allocated_buffer-inl.h"
3
#include "async_wrap-inl.h"
4
#include "base_object-inl.h"
5
#include "env-inl.h"
6
#include "memory_tracker-inl.h"
7
#include "string_bytes.h"
8
#include "threadpoolwork-inl.h"
9
#include "v8.h"
10
11
#include <cstdio>
12
13
namespace node {
14
15
using v8::FunctionCallbackInfo;
16
using v8::FunctionTemplate;
17
using v8::Just;
18
using v8::Local;
19
using v8::Maybe;
20
using v8::MaybeLocal;
21
using v8::Nothing;
22
using v8::Object;
23
using v8::Uint32;
24
using v8::Value;
25
26
namespace crypto {
27
1246
Hash::Hash(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
28
1246
  MakeWeak();
29
1246
}
30
31
void Hash::MemoryInfo(MemoryTracker* tracker) const {
32
  tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
33
  tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
34
}
35
36
4
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
37
4
  Environment* env = Environment::GetCurrent(args);
38
4
  CipherPushContext ctx(env);
39
4
  EVP_MD_do_all_sorted(array_push_back<EVP_MD>, &ctx);
40
8
  args.GetReturnValue().Set(ctx.ToJSArray());
41
4
}
42
43
4371
void Hash::Initialize(Environment* env, Local<Object> target) {
44
4371
  Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
45
46
8742
  t->InstanceTemplate()->SetInternalFieldCount(
47
      Hash::kInternalFieldCount);
48
4371
  t->Inherit(BaseObject::GetConstructorTemplate(env));
49
50
4371
  env->SetProtoMethod(t, "update", HashUpdate);
51
4371
  env->SetProtoMethod(t, "digest", HashDigest);
52
53
4371
  env->SetConstructorFunction(target, "Hash", t);
54
55
4371
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
56
57
4371
  HashJob::Initialize(env, target);
58
4371
}
59
60
4928
void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
61
4928
  registry->Register(New);
62
4928
  registry->Register(HashUpdate);
63
4928
  registry->Register(HashDigest);
64
4928
  registry->Register(GetHashes);
65
66
4928
  HashJob::RegisterExternalReferences(registry);
67
4928
}
68
69
1246
void Hash::New(const FunctionCallbackInfo<Value>& args) {
70
1246
  Environment* env = Environment::GetCurrent(args);
71
72
1246
  const Hash* orig = nullptr;
73
1246
  const EVP_MD* md = nullptr;
74
75
1246
  if (args[0]->IsObject()) {
76
13
    ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
77
5
    md = EVP_MD_CTX_md(orig->mdctx_.get());
78
  } else {
79
2482
    const Utf8Value hash_type(env->isolate(), args[0]);
80
1241
    md = EVP_get_digestbyname(*hash_type);
81
  }
82
83
1246
  Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
84
2492
  if (!args[1]->IsUndefined()) {
85
13
    CHECK(args[1]->IsUint32());
86
26
    xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
87
  }
88
89
1246
  Hash* hash = new Hash(env, args.This());
90

1246
  if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
91
3
    return ThrowCryptoError(env, ERR_get_error(),
92
3
                            "Digest method not supported");
93
  }
94
95

1248
  if (orig != nullptr &&
96
5
      0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
97
    return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
98
  }
99
}
100
101
1244
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
102
1244
  mdctx_.reset(EVP_MD_CTX_new());
103

1244
  if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
104
    mdctx_.reset();
105
    return false;
106
  }
107
108
1244
  md_len_ = EVP_MD_size(md);
109

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

1227
  if (!hash->digest_ && len > 0) {
158
    // Some hash algorithms such as SHA3 do not support calling
159
    // EVP_DigestFinal_ex more than once, however, Hash._flush
160
    // and Hash.digest can both be used to retrieve the digest,
161
    // so we need to cache it.
162
    // See https://github.com/nodejs/node/issues/28245.
163
164
1224
    char* md_value = MallocOpenSSL<char>(len);
165
1224
    ByteSource digest = ByteSource::Allocated(md_value, len);
166
167
1224
    size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
168
    int ret;
169
1224
    if (len == default_len) {
170
1218
      ret = EVP_DigestFinal_ex(
171
1218
          hash->mdctx_.get(),
172
          reinterpret_cast<unsigned char*>(md_value),
173
          &len);
174
      // The output length should always equal hash->md_len_
175
1218
      CHECK_EQ(len, hash->md_len_);
176
    } else {
177
6
      ret = EVP_DigestFinalXOF(
178
6
          hash->mdctx_.get(),
179
          reinterpret_cast<unsigned char*>(md_value),
180
          len);
181
    }
182
183
1224
    if (ret != 1)
184
      return ThrowCryptoError(env, ERR_get_error());
185
186
1224
    hash->digest_ = std::move(digest);
187
  }
188
189
  Local<Value> error;
190
  MaybeLocal<Value> rc =
191
      StringBytes::Encode(env->isolate(),
192
1227
                          hash->digest_.get(),
193
                          len,
194
                          encoding,
195
1227
                          &error);
196
1227
  if (rc.IsEmpty()) {
197
    CHECK(!error.IsEmpty());
198
    env->isolate()->ThrowException(error);
199
    return;
200
  }
201
3681
  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
202
}
203
204
141
HashConfig::HashConfig(HashConfig&& other) noexcept
205
141
    : mode(other.mode),
206
141
      in(std::move(other.in)),
207
141
      digest(other.digest),
208
141
      length(other.length) {}
209
210
HashConfig& HashConfig::operator=(HashConfig&& other) noexcept {
211
  if (&other == this) return *this;
212
  this->~HashConfig();
213
  return *new (this) HashConfig(std::move(other));
214
}
215
216
void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
217
  // If the Job is sync, then the HashConfig does not own the data.
218
  if (mode == kCryptoJobAsync)
219
    tracker->TrackFieldWithSize("in", in.size());
220
}
221
222
141
Maybe<bool> HashTraits::EncodeOutput(
223
    Environment* env,
224
    const HashConfig& params,
225
    ByteSource* out,
226
    v8::Local<v8::Value>* result) {
227
282
  *result = out->ToArrayBuffer(env);
228
141
  return Just(!result->IsEmpty());
229
}
230
231
154
Maybe<bool> HashTraits::AdditionalConfig(
232
    CryptoJobMode mode,
233
    const FunctionCallbackInfo<Value>& args,
234
    unsigned int offset,
235
    HashConfig* params) {
236
154
  Environment* env = Environment::GetCurrent(args);
237
238
154
  params->mode = mode;
239
240

462
  CHECK(args[offset]->IsString());  // Hash algorithm
241
462
  Utf8Value digest(env->isolate(), args[offset]);
242
154
  params->digest = EVP_get_digestbyname(*digest);
243
154
  if (UNLIKELY(params->digest == nullptr)) {
244
12
    THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
245
12
    return Nothing<bool>();
246
  }
247
248
426
  ArrayBufferOrViewContents<char> data(args[offset + 1]);
249
142
  if (UNLIKELY(!data.CheckSizeInt32())) {
250
    THROW_ERR_OUT_OF_RANGE(env, "data is too big");
251
    return Nothing<bool>();
252
  }
253
  params->in = mode == kCryptoJobAsync
254
284
      ? data.ToCopy()
255
142
      : data.ToByteSource();
256
257
142
  unsigned int expected = EVP_MD_size(params->digest);
258
142
  params->length = expected;
259

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


423
                   params.in.size()) <= 0)) {
287
    return false;
288
  }
289
290
141
  if (LIKELY(params.length > 0)) {
291
141
    unsigned int length = params.length;
292
141
    char* data = MallocOpenSSL<char>(length);
293
141
    ByteSource buf = ByteSource::Allocated(data, length);
294
141
    unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
295
296
141
    size_t expected = EVP_MD_CTX_size(ctx.get());
297
298
141
    int ret = (length == expected)
299
141
        ? EVP_DigestFinal_ex(ctx.get(), ptr, &length)
300
        : EVP_DigestFinalXOF(ctx.get(), ptr, length);
301
302
141
    if (UNLIKELY(ret != 1))
303
      return false;
304
305
141
    *out = std::move(buf);
306
  }
307
308
141
  return true;
309
}
310
311
}  // namespace crypto
312
}  // namespace node