GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: crypto/crypto_aes.cc Lines: 209 303 69.0 %
Date: 2022-06-21 04:15:54 Branches: 132 238 55.5 %

Line Branch Exec Source
1
#include "crypto/crypto_aes.h"
2
#include "async_wrap-inl.h"
3
#include "base_object-inl.h"
4
#include "crypto/crypto_cipher.h"
5
#include "crypto/crypto_keys.h"
6
#include "crypto/crypto_util.h"
7
#include "env-inl.h"
8
#include "memory_tracker-inl.h"
9
#include "threadpoolwork-inl.h"
10
#include "v8.h"
11
12
#include <openssl/bn.h>
13
#include <openssl/aes.h>
14
15
#include <vector>
16
17
namespace node {
18
19
using v8::FunctionCallbackInfo;
20
using v8::Just;
21
using v8::Local;
22
using v8::Maybe;
23
using v8::Nothing;
24
using v8::Object;
25
using v8::Uint32;
26
using v8::Value;
27
28
namespace crypto {
29
namespace {
30
// Implements general AES encryption and decryption for CBC
31
// The key_data must be a secret key.
32
// On success, this function sets out to a new ByteSource
33
// instance containing the results and returns WebCryptoCipherStatus::OK.
34
801
WebCryptoCipherStatus AES_Cipher(
35
    Environment* env,
36
    KeyObjectData* key_data,
37
    WebCryptoCipherMode cipher_mode,
38
    const AESCipherConfig& params,
39
    const ByteSource& in,
40
    ByteSource* out) {
41
801
  CHECK_NOT_NULL(key_data);
42
801
  CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
43
44
801
  const int mode = EVP_CIPHER_mode(params.cipher);
45
46
1602
  CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
47
801
  EVP_CIPHER_CTX_init(ctx.get());
48
801
  if (mode == EVP_CIPH_WRAP_MODE)
49
76
    EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
50
51
801
  const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
52
53
801
  if (!EVP_CipherInit_ex(
54
          ctx.get(),
55
801
          params.cipher,
56
          nullptr,
57
          nullptr,
58
          nullptr,
59
          encrypt)) {
60
    // Cipher init failed
61
    return WebCryptoCipherStatus::FAILED;
62
  }
63
64

1275
  if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
65
        ctx.get(),
66
        EVP_CTRL_AEAD_SET_IVLEN,
67
474
        params.iv.size(),
68
        nullptr)) {
69
    return WebCryptoCipherStatus::FAILED;
70
  }
71
72
801
  if (!EVP_CIPHER_CTX_set_key_length(
73
          ctx.get(),
74

2403
          key_data->GetSymmetricKeySize()) ||
75
1602
      !EVP_CipherInit_ex(
76
          ctx.get(),
77
          nullptr,
78
          nullptr,
79
801
          reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
80
          params.iv.data<unsigned char>(),
81
          encrypt)) {
82
    return WebCryptoCipherStatus::FAILED;
83
  }
84
85
801
  size_t tag_len = 0;
86
87
801
  if (mode == EVP_CIPH_GCM_MODE) {
88
474
    switch (cipher_mode) {
89
228
      case kWebCryptoCipherDecrypt:
90
        // If in decrypt mode, the auth tag must be set in the params.tag.
91
228
        CHECK(params.tag);
92
228
        if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
93
                                 EVP_CTRL_AEAD_SET_TAG,
94
228
                                 params.tag.size(),
95
228
                                 const_cast<char*>(params.tag.data<char>()))) {
96
          return WebCryptoCipherStatus::FAILED;
97
        }
98
228
        break;
99
246
      case kWebCryptoCipherEncrypt:
100
        // In decrypt mode, we grab the tag length here. We'll use it to
101
        // ensure that that allocated buffer has enough room for both the
102
        // final block and the auth tag. Unlike our other AES-GCM implementation
103
        // in CipherBase, in WebCrypto, the auth tag is concatenated to the end
104
        // of the generated ciphertext and returned in the same ArrayBuffer.
105
246
        tag_len = params.length;
106
246
        break;
107
      default:
108
        UNREACHABLE();
109
    }
110
  }
111
112
801
  size_t total = 0;
113
801
  int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
114
  int out_len;
115
116
474
  if (mode == EVP_CIPH_GCM_MODE &&
117

1275
      params.additional_data.size() &&
118
318
      !EVP_CipherUpdate(
119
            ctx.get(),
120
            nullptr,
121
            &out_len,
122
            params.additional_data.data<unsigned char>(),
123
318
            params.additional_data.size())) {
124
    return WebCryptoCipherStatus::FAILED;
125
  }
126
127
1602
  ByteSource::Builder buf(buf_len);
128
129
  // In some outdated version of OpenSSL (e.g.
130
  // ubi81_sharedlibs_openssl111fips_x64) may be used in sharedlib mode, the
131
  // logic will be failed when input size is zero. The newly OpenSSL has fixed
132
  // it up. But we still have to regard zero as special in Node.js code to
133
  // prevent old OpenSSL failure.
134
  //
135
  // Refs: https://github.com/openssl/openssl/commit/420cb707b880e4fb649094241371701013eeb15f
136
  // Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
137
801
  if (in.size() == 0) {
138
2
    out_len = 0;
139
799
  } else if (!EVP_CipherUpdate(ctx.get(),
140
                               buf.data<unsigned char>(),
141
                               &out_len,
142
                               in.data<unsigned char>(),
143
799
                               in.size())) {
144
    return WebCryptoCipherStatus::FAILED;
145
  }
146
147
801
  total += out_len;
148
801
  CHECK_LE(out_len, buf_len);
149
801
  out_len = EVP_CIPHER_CTX_block_size(ctx.get());
150
801
  if (!EVP_CipherFinal_ex(
151
801
          ctx.get(), buf.data<unsigned char>() + total, &out_len)) {
152
15
    return WebCryptoCipherStatus::FAILED;
153
  }
154
786
  total += out_len;
155
156
  // If using AES_GCM, grab the generated auth tag and append
157
  // it to the end of the ciphertext.
158

786
  if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
159
246
    if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
160
                             EVP_CTRL_AEAD_GET_TAG,
161
                             tag_len,
162
246
                             buf.data<unsigned char>() + total))
163
      return WebCryptoCipherStatus::FAILED;
164
246
    total += tag_len;
165
  }
166
167
  // It's possible that we haven't used the full allocated space. Size down.
168
786
  *out = std::move(buf).release(total);
169
170
786
  return WebCryptoCipherStatus::OK;
171
}
172
173
// The AES_CTR implementation here takes it's inspiration from the chromium
174
// implementation here:
175
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
176
177
template <typename T>
178
236
T CeilDiv(T a, T b) {
179
236
  return a == 0 ? 0 : 1 + (a - 1) / b;
180
}
181
182
236
BignumPointer GetCounter(const AESCipherConfig& params) {
183
236
  unsigned int remainder = (params.length % CHAR_BIT);
184
236
  const unsigned char* data = params.iv.data<unsigned char>();
185
186
236
  if (remainder == 0) {
187
236
    unsigned int byte_length = params.length / CHAR_BIT;
188
    return BignumPointer(BN_bin2bn(
189
472
        data + params.iv.size() - byte_length,
190
        byte_length,
191
236
        nullptr));
192
  }
193
194
  unsigned int byte_length =
195
      CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
196
197
  std::vector<unsigned char> counter(
198
      data + params.iv.size() - byte_length,
199
      data + params.iv.size());
200
  counter[0] &= ~(0xFF << remainder);
201
202
  return BignumPointer(BN_bin2bn(counter.data(), counter.size(), nullptr));
203
}
204
205
std::vector<unsigned char> BlockWithZeroedCounter(
206
    const AESCipherConfig& params) {
207
  unsigned int length_bytes = params.length / CHAR_BIT;
208
  unsigned int remainder = params.length % CHAR_BIT;
209
210
  const unsigned char* data = params.iv.data<unsigned char>();
211
212
  std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
213
214
  size_t index = new_counter_block.size() - length_bytes;
215
  memset(&new_counter_block.front() + index, 0, length_bytes);
216
217
  if (remainder)
218
    new_counter_block[index - 1] &= 0xFF << remainder;
219
220
  return new_counter_block;
221
}
222
223
236
WebCryptoCipherStatus AES_CTR_Cipher2(
224
    KeyObjectData* key_data,
225
    WebCryptoCipherMode cipher_mode,
226
    const AESCipherConfig& params,
227
    const ByteSource& in,
228
    unsigned const char* counter,
229
    unsigned char* out) {
230
472
  CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
231
236
  const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
232
233
236
  if (!EVP_CipherInit_ex(
234
          ctx.get(),
235
236
          params.cipher,
236
          nullptr,
237
236
          reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
238
          counter,
239
          encrypt)) {
240
    // Cipher init failed
241
    return WebCryptoCipherStatus::FAILED;
242
  }
243
244
236
  int out_len = 0;
245
236
  int final_len = 0;
246
236
  if (!EVP_CipherUpdate(
247
          ctx.get(),
248
          out,
249
          &out_len,
250
          in.data<unsigned char>(),
251
236
          in.size())) {
252
    return WebCryptoCipherStatus::FAILED;
253
  }
254
255
236
  if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
256
    return WebCryptoCipherStatus::FAILED;
257
258
236
  out_len += final_len;
259
236
  if (static_cast<unsigned>(out_len) != in.size())
260
    return WebCryptoCipherStatus::FAILED;
261
262
236
  return WebCryptoCipherStatus::OK;
263
}
264
265
236
WebCryptoCipherStatus AES_CTR_Cipher(
266
    Environment* env,
267
    KeyObjectData* key_data,
268
    WebCryptoCipherMode cipher_mode,
269
    const AESCipherConfig& params,
270
    const ByteSource& in,
271
    ByteSource* out) {
272
472
  BignumPointer num_counters(BN_new());
273
236
  if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
274
    return WebCryptoCipherStatus::FAILED;
275
276
472
  BignumPointer current_counter = GetCounter(params);
277
278
472
  BignumPointer num_output(BN_new());
279
280
236
  if (!BN_set_word(num_output.get(), CeilDiv(in.size(), kAesBlockSize)))
281
    return WebCryptoCipherStatus::FAILED;
282
283
  // Just like in chromium's implementation, if the counter will
284
  // be incremented more than there are counter values, we fail.
285
236
  if (BN_cmp(num_output.get(), num_counters.get()) > 0)
286
    return WebCryptoCipherStatus::FAILED;
287
288
472
  BignumPointer remaining_until_reset(BN_new());
289
236
  if (!BN_sub(remaining_until_reset.get(),
290
236
              num_counters.get(),
291
236
              current_counter.get())) {
292
    return WebCryptoCipherStatus::FAILED;
293
  }
294
295
  // Output size is identical to the input size.
296
472
  ByteSource::Builder buf(in.size());
297
298
  // Also just like in chromium's implementation, if we can process
299
  // the input without wrapping the counter, we'll do it as a single
300
  // call here. If we can't, we'll fallback to the a two-step approach
301
236
  if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
302
236
    auto status = AES_CTR_Cipher2(key_data,
303
                                  cipher_mode,
304
                                  params,
305
                                  in,
306
                                  params.iv.data<unsigned char>(),
307
                                  buf.data<unsigned char>());
308
236
    if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
309
236
    return status;
310
  }
311
312
  BN_ULONG blocks_part1 = BN_get_word(remaining_until_reset.get());
313
  BN_ULONG input_size_part1 = blocks_part1 * kAesBlockSize;
314
315
  // Encrypt the first part...
316
  auto status =
317
      AES_CTR_Cipher2(key_data,
318
                      cipher_mode,
319
                      params,
320
                      ByteSource::Foreign(in.data<char>(), input_size_part1),
321
                      params.iv.data<unsigned char>(),
322
                      buf.data<unsigned char>());
323
324
  if (status != WebCryptoCipherStatus::OK)
325
    return status;
326
327
  // Wrap the counter around to zero
328
  std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
329
330
  // Encrypt the second part...
331
  status =
332
      AES_CTR_Cipher2(key_data,
333
                      cipher_mode,
334
                      params,
335
                      ByteSource::Foreign(in.data<char>() + input_size_part1,
336
                                          in.size() - input_size_part1),
337
                      new_counter_block.data(),
338
                      buf.data<unsigned char>() + input_size_part1);
339
340
  if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
341
342
  return status;
343
}
344
345
961
bool ValidateIV(
346
    Environment* env,
347
    CryptoJobMode mode,
348
    Local<Value> value,
349
    AESCipherConfig* params) {
350
1922
  ArrayBufferOrViewContents<char> iv(value);
351
961
  if (UNLIKELY(!iv.CheckSizeInt32())) {
352
    THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
353
    return false;
354
  }
355
  params->iv = (mode == kCryptoJobAsync)
356
1922
      ? iv.ToCopy()
357
961
      : iv.ToByteSource();
358
961
  return true;
359
}
360
361
236
bool ValidateCounter(
362
  Environment* env,
363
  Local<Value> value,
364
  AESCipherConfig* params) {
365
236
  CHECK(value->IsUint32());  // Length
366
236
  params->length = value.As<Uint32>()->Value();
367
236
  if (params->iv.size() != 16 ||
368

472
      params->length == 0 ||
369
236
      params->length > 128) {
370
    THROW_ERR_CRYPTO_INVALID_COUNTER(env);
371
    return false;
372
  }
373
236
  return true;
374
}
375
376
474
bool ValidateAuthTag(
377
    Environment* env,
378
    CryptoJobMode mode,
379
    WebCryptoCipherMode cipher_mode,
380
    Local<Value> value,
381
    AESCipherConfig* params) {
382
474
  switch (cipher_mode) {
383
228
    case kWebCryptoCipherDecrypt: {
384
228
      if (!IsAnyByteSource(value)) {
385
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
386
        return false;
387
      }
388
228
      ArrayBufferOrViewContents<char> tag_contents(value);
389
228
      if (UNLIKELY(!tag_contents.CheckSizeInt32())) {
390
        THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
391
        return false;
392
      }
393
      params->tag = mode == kCryptoJobAsync
394
456
          ? tag_contents.ToCopy()
395
228
          : tag_contents.ToByteSource();
396
228
      break;
397
    }
398
246
    case kWebCryptoCipherEncrypt: {
399
246
      if (!value->IsUint32()) {
400
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
401
        return false;
402
      }
403
246
      params->length = value.As<Uint32>()->Value();
404
246
      if (params->length > 128) {
405
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
406
        return false;
407
      }
408
246
      break;
409
    }
410
    default:
411
      UNREACHABLE();
412
  }
413
474
  return true;
414
}
415
416
474
bool ValidateAdditionalData(
417
    Environment* env,
418
    CryptoJobMode mode,
419
    Local<Value> value,
420
    AESCipherConfig* params) {
421
  // Additional Data
422
474
  if (IsAnyByteSource(value)) {
423
318
    ArrayBufferOrViewContents<char> additional(value);
424
318
    if (UNLIKELY(!additional.CheckSizeInt32())) {
425
      THROW_ERR_OUT_OF_RANGE(env, "additionalData is too big");
426
      return false;
427
    }
428
    params->additional_data = mode == kCryptoJobAsync
429
636
        ? additional.ToCopy()
430
318
        : additional.ToByteSource();
431
  }
432
474
  return true;
433
}
434
435
76
void UseDefaultIV(AESCipherConfig* params) {
436
76
  params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
437
76
}
438
}  // namespace
439
440
1037
AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
441
1037
    : mode(other.mode),
442
1037
      variant(other.variant),
443
1037
      cipher(other.cipher),
444
1037
      length(other.length),
445
1037
      iv(std::move(other.iv)),
446
1037
      additional_data(std::move(other.additional_data)),
447
1037
      tag(std::move(other.tag)) {}
448
449
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
450
  if (&other == this) return *this;
451
  this->~AESCipherConfig();
452
  return *new (this) AESCipherConfig(std::move(other));
453
}
454
455
void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
456
  // If mode is sync, then the data in each of these properties
457
  // is not owned by the AESCipherConfig, so we ignore it.
458
  if (mode == kCryptoJobAsync) {
459
    tracker->TrackFieldWithSize("iv", iv.size());
460
    tracker->TrackFieldWithSize("additional_data", additional_data.size());
461
    tracker->TrackFieldWithSize("tag", tag.size());
462
  }
463
}
464
465
1037
Maybe<bool> AESCipherTraits::AdditionalConfig(
466
    CryptoJobMode mode,
467
    const FunctionCallbackInfo<Value>& args,
468
    unsigned int offset,
469
    WebCryptoCipherMode cipher_mode,
470
    AESCipherConfig* params) {
471
1037
  Environment* env = Environment::GetCurrent(args);
472
473
1037
  params->mode = mode;
474
475

2074
  CHECK(args[offset]->IsUint32());  // Key Variant
476
1037
  params->variant =
477
3111
      static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
478
479
  int cipher_nid;
480
481



1037
  switch (params->variant) {
482
223
    case kKeyVariantAES_CTR_128:
483

669
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
484

446
          !ValidateCounter(env, args[offset + 2], params)) {
485
        return Nothing<bool>();
486
      }
487
223
      cipher_nid = NID_aes_128_ctr;
488
223
      break;
489
4
    case kKeyVariantAES_CTR_192:
490

12
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
491

8
          !ValidateCounter(env, args[offset + 2], params)) {
492
        return Nothing<bool>();
493
      }
494
4
      cipher_nid = NID_aes_192_ctr;
495
4
      break;
496
9
    case kKeyVariantAES_CTR_256:
497

27
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
498

18
          !ValidateCounter(env, args[offset + 2], params)) {
499
        return Nothing<bool>();
500
      }
501
9
      cipher_nid = NID_aes_256_ctr;
502
9
      break;
503
229
    case kKeyVariantAES_CBC_128:
504

458
      if (!ValidateIV(env, mode, args[offset + 1], params))
505
        return Nothing<bool>();
506
229
      cipher_nid = NID_aes_128_cbc;
507
229
      break;
508
7
    case kKeyVariantAES_CBC_192:
509

14
      if (!ValidateIV(env, mode, args[offset + 1], params))
510
        return Nothing<bool>();
511
7
      cipher_nid = NID_aes_192_cbc;
512
7
      break;
513
15
    case kKeyVariantAES_CBC_256:
514

30
      if (!ValidateIV(env, mode, args[offset + 1], params))
515
        return Nothing<bool>();
516
15
      cipher_nid = NID_aes_256_cbc;
517
15
      break;
518
76
    case kKeyVariantAES_KW_128:
519
76
      UseDefaultIV(params);
520
76
      cipher_nid = NID_id_aes128_wrap;
521
76
      break;
522
    case kKeyVariantAES_KW_192:
523
      UseDefaultIV(params);
524
      cipher_nid = NID_id_aes192_wrap;
525
      break;
526
    case kKeyVariantAES_KW_256:
527
      UseDefaultIV(params);
528
      cipher_nid = NID_id_aes256_wrap;
529
      break;
530
314
    case kKeyVariantAES_GCM_128:
531
628
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
532


942
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
533

628
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
534
        return Nothing<bool>();
535
      }
536
314
      cipher_nid = NID_aes_128_gcm;
537
314
      break;
538
56
    case kKeyVariantAES_GCM_192:
539
112
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
540


168
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
541

112
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
542
        return Nothing<bool>();
543
      }
544
56
      cipher_nid = NID_aes_192_gcm;
545
56
      break;
546
104
    case kKeyVariantAES_GCM_256:
547
208
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
548


312
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
549

208
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
550
        return Nothing<bool>();
551
      }
552
104
      cipher_nid = NID_aes_256_gcm;
553
104
      break;
554
    default:
555
      UNREACHABLE();
556
  }
557
558
1037
  params->cipher = EVP_get_cipherbynid(cipher_nid);
559
1037
  CHECK_NOT_NULL(params->cipher);
560
561
1037
  if (params->iv.size() <
562
1037
      static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
563
    THROW_ERR_CRYPTO_INVALID_IV(env);
564
    return Nothing<bool>();
565
  }
566
567
1037
  return Just(true);
568
}
569
570
1037
WebCryptoCipherStatus AESCipherTraits::DoCipher(
571
    Environment* env,
572
    std::shared_ptr<KeyObjectData> key_data,
573
    WebCryptoCipherMode cipher_mode,
574
    const AESCipherConfig& params,
575
    const ByteSource& in,
576
    ByteSource* out) {
577
#define V(name, fn)                                                           \
578
  case kKeyVariantAES_ ## name:                                               \
579
    return fn(env, key_data.get(), cipher_mode, params, in, out);
580



1037
  switch (params.variant) {
581
1037
    VARIANTS(V)
582
    default:
583
      UNREACHABLE();
584
  }
585
#undef V
586
}
587
588
1274
void AES::Initialize(Environment* env, Local<Object> target) {
589
1274
  AESCryptoJob::Initialize(env, target);
590
591
#define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
592
30576
  VARIANTS(V)
593
#undef V
594
1274
}
595
596
5213
void AES::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
597
5213
  AESCryptoJob::RegisterExternalReferences(registry);
598
5213
}
599
600
}  // namespace crypto
601
}  // namespace node