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.
AES_Cipher(Environment * env,KeyObjectData * key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,ByteSource * out)34 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 CHECK_NOT_NULL(key_data);
42 CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
43
44 const int mode = EVP_CIPHER_mode(params.cipher);
45
46 CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
47 EVP_CIPHER_CTX_init(ctx.get());
48 if (mode == EVP_CIPH_WRAP_MODE)
49 EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
50
51 const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
52
53 if (!EVP_CipherInit_ex(
54 ctx.get(),
55 params.cipher,
56 nullptr,
57 nullptr,
58 nullptr,
59 encrypt)) {
60 // Cipher init failed
61 return WebCryptoCipherStatus::FAILED;
62 }
63
64 if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
65 ctx.get(),
66 EVP_CTRL_AEAD_SET_IVLEN,
67 params.iv.size(),
68 nullptr)) {
69 return WebCryptoCipherStatus::FAILED;
70 }
71
72 if (!EVP_CIPHER_CTX_set_key_length(
73 ctx.get(),
74 key_data->GetSymmetricKeySize()) ||
75 !EVP_CipherInit_ex(
76 ctx.get(),
77 nullptr,
78 nullptr,
79 reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
80 params.iv.data<unsigned char>(),
81 encrypt)) {
82 return WebCryptoCipherStatus::FAILED;
83 }
84
85 size_t tag_len = 0;
86
87 if (mode == EVP_CIPH_GCM_MODE) {
88 switch (cipher_mode) {
89 case kWebCryptoCipherDecrypt:
90 // If in decrypt mode, the auth tag must be set in the params.tag.
91 CHECK(params.tag);
92 if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
93 EVP_CTRL_AEAD_SET_TAG,
94 params.tag.size(),
95 const_cast<char*>(params.tag.data<char>()))) {
96 return WebCryptoCipherStatus::FAILED;
97 }
98 break;
99 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 tag_len = params.length;
106 break;
107 default:
108 UNREACHABLE();
109 }
110 }
111
112 size_t total = 0;
113 int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
114 int out_len;
115
116 if (mode == EVP_CIPH_GCM_MODE &&
117 params.additional_data.size() &&
118 !EVP_CipherUpdate(
119 ctx.get(),
120 nullptr,
121 &out_len,
122 params.additional_data.data<unsigned char>(),
123 params.additional_data.size())) {
124 return WebCryptoCipherStatus::FAILED;
125 }
126
127 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 if (in.size() == 0) {
138 out_len = 0;
139 } else if (!EVP_CipherUpdate(ctx.get(),
140 buf.data<unsigned char>(),
141 &out_len,
142 in.data<unsigned char>(),
143 in.size())) {
144 return WebCryptoCipherStatus::FAILED;
145 }
146
147 total += out_len;
148 CHECK_LE(out_len, buf_len);
149 out_len = EVP_CIPHER_CTX_block_size(ctx.get());
150 if (!EVP_CipherFinal_ex(
151 ctx.get(), buf.data<unsigned char>() + total, &out_len)) {
152 return WebCryptoCipherStatus::FAILED;
153 }
154 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 if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
159 if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
160 EVP_CTRL_AEAD_GET_TAG,
161 tag_len,
162 buf.data<unsigned char>() + total))
163 return WebCryptoCipherStatus::FAILED;
164 total += tag_len;
165 }
166
167 // It's possible that we haven't used the full allocated space. Size down.
168 *out = std::move(buf).release(total);
169
170 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>
CeilDiv(T a,T b)178 T CeilDiv(T a, T b) {
179 return a == 0 ? 0 : 1 + (a - 1) / b;
180 }
181
GetCounter(const AESCipherConfig & params)182 BignumPointer GetCounter(const AESCipherConfig& params) {
183 unsigned int remainder = (params.length % CHAR_BIT);
184 const unsigned char* data = params.iv.data<unsigned char>();
185
186 if (remainder == 0) {
187 unsigned int byte_length = params.length / CHAR_BIT;
188 return BignumPointer(BN_bin2bn(
189 data + params.iv.size() - byte_length,
190 byte_length,
191 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
BlockWithZeroedCounter(const AESCipherConfig & params)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
AES_CTR_Cipher2(KeyObjectData * key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,unsigned const char * counter,unsigned char * out)223 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 CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
231 const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
232
233 if (!EVP_CipherInit_ex(
234 ctx.get(),
235 params.cipher,
236 nullptr,
237 reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
238 counter,
239 encrypt)) {
240 // Cipher init failed
241 return WebCryptoCipherStatus::FAILED;
242 }
243
244 int out_len = 0;
245 int final_len = 0;
246 if (!EVP_CipherUpdate(
247 ctx.get(),
248 out,
249 &out_len,
250 in.data<unsigned char>(),
251 in.size())) {
252 return WebCryptoCipherStatus::FAILED;
253 }
254
255 if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
256 return WebCryptoCipherStatus::FAILED;
257
258 out_len += final_len;
259 if (static_cast<unsigned>(out_len) != in.size())
260 return WebCryptoCipherStatus::FAILED;
261
262 return WebCryptoCipherStatus::OK;
263 }
264
AES_CTR_Cipher(Environment * env,KeyObjectData * key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,ByteSource * out)265 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 BignumPointer num_counters(BN_new());
273 if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
274 return WebCryptoCipherStatus::FAILED;
275
276 BignumPointer current_counter = GetCounter(params);
277
278 BignumPointer num_output(BN_new());
279
280 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 if (BN_cmp(num_output.get(), num_counters.get()) > 0)
286 return WebCryptoCipherStatus::FAILED;
287
288 BignumPointer remaining_until_reset(BN_new());
289 if (!BN_sub(remaining_until_reset.get(),
290 num_counters.get(),
291 current_counter.get())) {
292 return WebCryptoCipherStatus::FAILED;
293 }
294
295 // Output size is identical to the input size.
296 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 if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
302 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 if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
309 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
ValidateIV(Environment * env,CryptoJobMode mode,Local<Value> value,AESCipherConfig * params)345 bool ValidateIV(
346 Environment* env,
347 CryptoJobMode mode,
348 Local<Value> value,
349 AESCipherConfig* params) {
350 ArrayBufferOrViewContents<char> iv(value);
351 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 ? iv.ToCopy()
357 : iv.ToByteSource();
358 return true;
359 }
360
ValidateCounter(Environment * env,Local<Value> value,AESCipherConfig * params)361 bool ValidateCounter(
362 Environment* env,
363 Local<Value> value,
364 AESCipherConfig* params) {
365 CHECK(value->IsUint32()); // Length
366 params->length = value.As<Uint32>()->Value();
367 if (params->iv.size() != 16 ||
368 params->length == 0 ||
369 params->length > 128) {
370 THROW_ERR_CRYPTO_INVALID_COUNTER(env);
371 return false;
372 }
373 return true;
374 }
375
ValidateAuthTag(Environment * env,CryptoJobMode mode,WebCryptoCipherMode cipher_mode,Local<Value> value,AESCipherConfig * params)376 bool ValidateAuthTag(
377 Environment* env,
378 CryptoJobMode mode,
379 WebCryptoCipherMode cipher_mode,
380 Local<Value> value,
381 AESCipherConfig* params) {
382 switch (cipher_mode) {
383 case kWebCryptoCipherDecrypt: {
384 if (!IsAnyByteSource(value)) {
385 THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
386 return false;
387 }
388 ArrayBufferOrViewContents<char> tag_contents(value);
389 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 ? tag_contents.ToCopy()
395 : tag_contents.ToByteSource();
396 break;
397 }
398 case kWebCryptoCipherEncrypt: {
399 if (!value->IsUint32()) {
400 THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
401 return false;
402 }
403 params->length = value.As<Uint32>()->Value();
404 if (params->length > 128) {
405 THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
406 return false;
407 }
408 break;
409 }
410 default:
411 UNREACHABLE();
412 }
413 return true;
414 }
415
ValidateAdditionalData(Environment * env,CryptoJobMode mode,Local<Value> value,AESCipherConfig * params)416 bool ValidateAdditionalData(
417 Environment* env,
418 CryptoJobMode mode,
419 Local<Value> value,
420 AESCipherConfig* params) {
421 // Additional Data
422 if (IsAnyByteSource(value)) {
423 ArrayBufferOrViewContents<char> additional(value);
424 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 ? additional.ToCopy()
430 : additional.ToByteSource();
431 }
432 return true;
433 }
434
UseDefaultIV(AESCipherConfig * params)435 void UseDefaultIV(AESCipherConfig* params) {
436 params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
437 }
438 } // namespace
439
AESCipherConfig(AESCipherConfig && other)440 AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
441 : mode(other.mode),
442 variant(other.variant),
443 cipher(other.cipher),
444 length(other.length),
445 iv(std::move(other.iv)),
446 additional_data(std::move(other.additional_data)),
447 tag(std::move(other.tag)) {}
448
operator =(AESCipherConfig && other)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
MemoryInfo(MemoryTracker * tracker) const455 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
AdditionalConfig(CryptoJobMode mode,const FunctionCallbackInfo<Value> & args,unsigned int offset,WebCryptoCipherMode cipher_mode,AESCipherConfig * params)465 Maybe<bool> AESCipherTraits::AdditionalConfig(
466 CryptoJobMode mode,
467 const FunctionCallbackInfo<Value>& args,
468 unsigned int offset,
469 WebCryptoCipherMode cipher_mode,
470 AESCipherConfig* params) {
471 Environment* env = Environment::GetCurrent(args);
472
473 params->mode = mode;
474
475 CHECK(args[offset]->IsUint32()); // Key Variant
476 params->variant =
477 static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
478
479 int cipher_nid;
480
481 switch (params->variant) {
482 case kKeyVariantAES_CTR_128:
483 if (!ValidateIV(env, mode, args[offset + 1], params) ||
484 !ValidateCounter(env, args[offset + 2], params)) {
485 return Nothing<bool>();
486 }
487 cipher_nid = NID_aes_128_ctr;
488 break;
489 case kKeyVariantAES_CTR_192:
490 if (!ValidateIV(env, mode, args[offset + 1], params) ||
491 !ValidateCounter(env, args[offset + 2], params)) {
492 return Nothing<bool>();
493 }
494 cipher_nid = NID_aes_192_ctr;
495 break;
496 case kKeyVariantAES_CTR_256:
497 if (!ValidateIV(env, mode, args[offset + 1], params) ||
498 !ValidateCounter(env, args[offset + 2], params)) {
499 return Nothing<bool>();
500 }
501 cipher_nid = NID_aes_256_ctr;
502 break;
503 case kKeyVariantAES_CBC_128:
504 if (!ValidateIV(env, mode, args[offset + 1], params))
505 return Nothing<bool>();
506 cipher_nid = NID_aes_128_cbc;
507 break;
508 case kKeyVariantAES_CBC_192:
509 if (!ValidateIV(env, mode, args[offset + 1], params))
510 return Nothing<bool>();
511 cipher_nid = NID_aes_192_cbc;
512 break;
513 case kKeyVariantAES_CBC_256:
514 if (!ValidateIV(env, mode, args[offset + 1], params))
515 return Nothing<bool>();
516 cipher_nid = NID_aes_256_cbc;
517 break;
518 case kKeyVariantAES_KW_128:
519 UseDefaultIV(params);
520 cipher_nid = NID_id_aes128_wrap;
521 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 case kKeyVariantAES_GCM_128:
531 if (!ValidateIV(env, mode, args[offset + 1], params) ||
532 !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
533 !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
534 return Nothing<bool>();
535 }
536 cipher_nid = NID_aes_128_gcm;
537 break;
538 case kKeyVariantAES_GCM_192:
539 if (!ValidateIV(env, mode, args[offset + 1], params) ||
540 !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
541 !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
542 return Nothing<bool>();
543 }
544 cipher_nid = NID_aes_192_gcm;
545 break;
546 case kKeyVariantAES_GCM_256:
547 if (!ValidateIV(env, mode, args[offset + 1], params) ||
548 !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
549 !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
550 return Nothing<bool>();
551 }
552 cipher_nid = NID_aes_256_gcm;
553 break;
554 default:
555 UNREACHABLE();
556 }
557
558 params->cipher = EVP_get_cipherbynid(cipher_nid);
559 if (params->cipher == nullptr) {
560 THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
561 return Nothing<bool>();
562 }
563
564 if (params->iv.size() <
565 static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
566 THROW_ERR_CRYPTO_INVALID_IV(env);
567 return Nothing<bool>();
568 }
569
570 return Just(true);
571 }
572
DoCipher(Environment * env,std::shared_ptr<KeyObjectData> key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,ByteSource * out)573 WebCryptoCipherStatus AESCipherTraits::DoCipher(
574 Environment* env,
575 std::shared_ptr<KeyObjectData> key_data,
576 WebCryptoCipherMode cipher_mode,
577 const AESCipherConfig& params,
578 const ByteSource& in,
579 ByteSource* out) {
580 #define V(name, fn) \
581 case kKeyVariantAES_ ## name: \
582 return fn(env, key_data.get(), cipher_mode, params, in, out);
583 switch (params.variant) {
584 VARIANTS(V)
585 default:
586 UNREACHABLE();
587 }
588 #undef V
589 }
590
Initialize(Environment * env,Local<Object> target)591 void AES::Initialize(Environment* env, Local<Object> target) {
592 AESCryptoJob::Initialize(env, target);
593
594 #define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
595 VARIANTS(V)
596 #undef V
597 }
598
RegisterExternalReferences(ExternalReferenceRegistry * registry)599 void AES::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
600 AESCryptoJob::RegisterExternalReferences(registry);
601 }
602
603 } // namespace crypto
604 } // namespace node
605