GeezerVison is showing github.com — crypto: refactor WebCrypto AEAD algorithms auth tag handling · nodejs/node@4d96e53
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/aes.js +6-58Lines changed: 6 additions & 58 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | 'use strict'; | 2 | 2 |
| 3 | 3 | const { | 4 | | - ArrayBufferIsView, | 5 | | - ArrayBufferPrototypeSlice, | 6 | 4 | ArrayFrom, | 7 | 5 | ArrayPrototypePush, | 8 | 6 | SafeSet, | 9 | | - TypedArrayPrototypeSlice, | 10 | 7 | } = primordials; | 11 | 8 |
| 12 | 9 | const { | @@ -28,8 +25,6 @@ const { | 28 | 25 | kKeyVariantAES_GCM_256, | 29 | 26 | kKeyVariantAES_KW_256, | 30 | 27 | kKeyVariantAES_OCB_256, | 31 | | - kWebCryptoCipherDecrypt, | 32 | | - kWebCryptoCipherEncrypt, | 33 | 28 | } = internalBinding('crypto'); | 34 | 29 |
| 35 | 30 | const { | @@ -143,80 +138,33 @@ function asyncAesKwCipher(mode, key, data) { | 143 | 138 | getVariant('AES-KW', key[kAlgorithm].length))); | 144 | 139 | } | 145 | 140 |
| 146 | | -async function asyncAesGcmCipher(mode, key, data, algorithm) { | | 141 | +function asyncAesGcmCipher(mode, key, data, algorithm) { | 147 | 142 | const { tagLength = 128 } = algorithm; | 148 | | - | 149 | 143 | const tagByteLength = tagLength / 8; | 150 | | - let tag; | 151 | | - switch (mode) { | 152 | | - case kWebCryptoCipherDecrypt: { | 153 | | - const slice = ArrayBufferIsView(data) ? | 154 | | - TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; | 155 | | - tag = slice(data, -tagByteLength); | 156 | | - | 157 | | - // Refs: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-operations | 158 | | - // | 159 | | - // > If *plaintext* has a length less than *tagLength* bits, then `throw` | 160 | | - // > an `OperationError`. | 161 | | - if (tagByteLength > tag.byteLength) { | 162 | | - throw lazyDOMException( | 163 | | - 'The provided data is too small.', | 164 | | - 'OperationError'); | 165 | | - } | 166 | 144 |
| 167 | | - data = slice(data, 0, -tagByteLength); | 168 | | - break; | 169 | | - } | 170 | | - case kWebCryptoCipherEncrypt: | 171 | | - tag = tagByteLength; | 172 | | - break; | 173 | | - } | 174 | | - | 175 | | - return await jobPromise(() => new AESCipherJob( | | 145 | + return jobPromise(() => new AESCipherJob( | 176 | 146 | kCryptoJobAsync, | 177 | 147 | mode, | 178 | 148 | key[kKeyObject][kHandle], | 179 | 149 | data, | 180 | 150 | getVariant('AES-GCM', key[kAlgorithm].length), | 181 | 151 | algorithm.iv, | 182 | | - tag, | | 152 | + tagByteLength, | 183 | 153 | algorithm.additionalData)); | 184 | 154 | } | 185 | 155 |
| 186 | | -async function asyncAesOcbCipher(mode, key, data, algorithm) { | | 156 | +function asyncAesOcbCipher(mode, key, data, algorithm) { | 187 | 157 | const { tagLength = 128 } = algorithm; | 188 | | - | 189 | 158 | const tagByteLength = tagLength / 8; | 190 | | - let tag; | 191 | | - switch (mode) { | 192 | | - case kWebCryptoCipherDecrypt: { | 193 | | - const slice = ArrayBufferIsView(data) ? | 194 | | - TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; | 195 | | - tag = slice(data, -tagByteLength); | 196 | | - | 197 | | - // Similar to GCM, OCB requires the tag to be present for decryption | 198 | | - if (tagByteLength > tag.byteLength) { | 199 | | - throw lazyDOMException( | 200 | | - 'The provided data is too small.', | 201 | | - 'OperationError'); | 202 | | - } | 203 | 159 |
| 204 | | - data = slice(data, 0, -tagByteLength); | 205 | | - break; | 206 | | - } | 207 | | - case kWebCryptoCipherEncrypt: | 208 | | - tag = tagByteLength; | 209 | | - break; | 210 | | - } | 211 | | - | 212 | | - return await jobPromise(() => new AESCipherJob( | | 160 | + return jobPromise(() => new AESCipherJob( | 213 | 161 | kCryptoJobAsync, | 214 | 162 | mode, | 215 | 163 | key[kKeyObject][kHandle], | 216 | 164 | data, | 217 | 165 | getVariant('AES-OCB', key.algorithm.length), | 218 | 166 | algorithm.iv, | 219 | | - tag, | | 167 | + tagByteLength, | 220 | 168 | algorithm.additionalData)); | 221 | 169 | } | 222 | 170 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/chacha20_poly1305.js +2-29Lines changed: 2 additions & 29 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | 'use strict'; | 2 | 2 |
| 3 | 3 | const { | 4 | | - ArrayBufferIsView, | 5 | | - ArrayBufferPrototypeSlice, | 6 | 4 | ArrayFrom, | 7 | 5 | SafeSet, | 8 | | - TypedArrayPrototypeSlice, | 9 | 6 | } = primordials; | 10 | 7 |
| 11 | 8 | const { | 12 | 9 | ChaCha20Poly1305CipherJob, | 13 | 10 | KeyObjectHandle, | 14 | 11 | kCryptoJobAsync, | 15 | | - kWebCryptoCipherDecrypt, | 16 | | - kWebCryptoCipherEncrypt, | 17 | 12 | } = internalBinding('crypto'); | 18 | 13 |
| 19 | 14 | const { | @@ -46,35 +41,13 @@ function validateKeyLength(length) { | 46 | 41 | throw lazyDOMException('Invalid key length', 'DataError'); | 47 | 42 | } | 48 | 43 |
| 49 | | -async function c20pCipher(mode, key, data, algorithm) { | 50 | | - let tag; | 51 | | - switch (mode) { | 52 | | - case kWebCryptoCipherDecrypt: { | 53 | | - const slice = ArrayBufferIsView(data) ? | 54 | | - TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; | 55 | | - | 56 | | - if (data.byteLength < 16) { | 57 | | - throw lazyDOMException( | 58 | | - 'The provided data is too small.', | 59 | | - 'OperationError'); | 60 | | - } | 61 | | - | 62 | | - tag = slice(data, -16); | 63 | | - data = slice(data, 0, -16); | 64 | | - break; | 65 | | - } | 66 | | - case kWebCryptoCipherEncrypt: | 67 | | - tag = 16; | 68 | | - break; | 69 | | - } | 70 | | - | 71 | | - return await jobPromise(() => new ChaCha20Poly1305CipherJob( | | 44 | +function c20pCipher(mode, key, data, algorithm) { | | 45 | + return jobPromise(() => new ChaCha20Poly1305CipherJob( | 72 | 46 | kCryptoJobAsync, | 73 | 47 | mode, | 74 | 48 | key[kKeyObject][kHandle], | 75 | 49 | data, | 76 | 50 | algorithm.iv, | 77 | | - tag, | 78 | 51 | algorithm.additionalData)); | 79 | 52 | } | 80 | 53 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_aes.cc +27-58Lines changed: 27 additions & 58 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -76,39 +76,35 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, | 76 | 76 | } | 77 | 77 |
| 78 | 78 | size_t tag_len = 0; | | 79 | + size_t data_len = in.size(); | 79 | 80 |
| 80 | 81 | if (params.cipher.isGcmMode() || params.cipher.isOcbMode()) { | | 82 | + tag_len = params.length; | 81 | 83 | switch (cipher_mode) { | 82 | 84 | case kWebCryptoCipherDecrypt: { | 83 | | - // If in decrypt mode, the auth tag must be set in the params.tag. | 84 | | - CHECK(params.tag); | | 85 | + // In decrypt mode, the auth tag is appended to the end of the | | 86 | + // ciphertext. Split it off and set it on the cipher context. | | 87 | + if (data_len < tag_len) { | | 88 | + return WebCryptoCipherStatus::FAILED; | | 89 | + } | | 90 | + data_len -= tag_len; | 85 | 91 |
| 86 | | - // For OCB mode, we need to set the auth tag length before setting the | 87 | | - // tag | 88 | 92 | if (params.cipher.isOcbMode()) { | 89 | | - if (!ctx.setAeadTagLength(params.tag.size())) { | | 93 | + if (!ctx.setAeadTagLength(tag_len)) { | 90 | 94 | return WebCryptoCipherStatus::FAILED; | 91 | 95 | } | 92 | 96 | } | 93 | 97 |
| 94 | 98 | ncrypto::Buffer<const char> buffer = { | 95 | | - .data = params.tag.data<char>(), | 96 | | - .len = params.tag.size(), | | 99 | + .data = in.data<char>() + data_len, | | 100 | + .len = tag_len, | 97 | 101 | }; | 98 | 102 | if (!ctx.setAeadTag(buffer)) { | 99 | 103 | return WebCryptoCipherStatus::FAILED; | 100 | 104 | } | 101 | 105 | break; | 102 | 106 | } | 103 | 107 | case kWebCryptoCipherEncrypt: { | 104 | | - // In encrypt mode, we grab the tag length here. We'll use it to | 105 | | - // ensure that that allocated buffer has enough room for both the | 106 | | - // final block and the auth tag. Unlike our other AES-GCM implementation | 107 | | - // in CipherBase, in WebCrypto, the auth tag is concatenated to the end | 108 | | - // of the generated ciphertext and returned in the same ArrayBuffer. | 109 | | - tag_len = params.length; | 110 | | - | 111 | | - // For OCB mode, we need to set the auth tag length | 112 | 108 | if (params.cipher.isOcbMode()) { | 113 | 109 | if (!ctx.setAeadTagLength(tag_len)) { | 114 | 110 | return WebCryptoCipherStatus::FAILED; | @@ -122,7 +118,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, | 122 | 118 | } | 123 | 119 |
| 124 | 120 | size_t total = 0; | 125 | | - int buf_len = in.size() + ctx.getBlockSize() + tag_len; | | 121 | + int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0); | 126 | 122 | int out_len; | 127 | 123 |
| 128 | 124 | ncrypto::Buffer<const unsigned char> buffer = { | @@ -148,9 +144,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, | 148 | 144 | // Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244 | 149 | 145 | buffer = { | 150 | 146 | .data = in.data<unsigned char>(), | 151 | | - .len = in.size(), | | 147 | + .len = data_len, | 152 | 148 | }; | 153 | | - if (in.empty()) { | | 149 | + if (data_len == 0) { | 154 | 150 | out_len = 0; | 155 | 151 | } else if (!ctx.update(buffer, ptr, &out_len)) { | 156 | 152 | return WebCryptoCipherStatus::FAILED; | @@ -381,42 +377,17 @@ bool ValidateCounter( | 381 | 377 | return true; | 382 | 378 | } | 383 | 379 |
| 384 | | -bool ValidateAuthTag( | 385 | | - Environment* env, | 386 | | - CryptoJobMode mode, | 387 | | - WebCryptoCipherMode cipher_mode, | 388 | | - Local<Value> value, | 389 | | - AESCipherConfig* params) { | 390 | | - switch (cipher_mode) { | 391 | | - case kWebCryptoCipherDecrypt: { | 392 | | - if (!IsAnyBufferSource(value)) { | 393 | | - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); | 394 | | - return false; | 395 | | - } | 396 | | - ArrayBufferOrViewContents<char> tag_contents(value); | 397 | | - if (!tag_contents.CheckSizeInt32()) [[unlikely]] { | 398 | | - THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big"); | 399 | | - return false; | 400 | | - } | 401 | | - params->tag = mode == kCryptoJobAsync | 402 | | - ? tag_contents.ToCopy() | 403 | | - : tag_contents.ToByteSource(); | 404 | | - break; | 405 | | - } | 406 | | - case kWebCryptoCipherEncrypt: { | 407 | | - if (!value->IsUint32()) { | 408 | | - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); | 409 | | - return false; | 410 | | - } | 411 | | - params->length = value.As<Uint32>()->Value(); | 412 | | - if (params->length > 128) { | 413 | | - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); | 414 | | - return false; | 415 | | - } | 416 | | - break; | 417 | | - } | 418 | | - default: | 419 | | - UNREACHABLE(); | | 380 | +bool ValidateAuthTag(Environment* env, | | 381 | + Local<Value> value, | | 382 | + AESCipherConfig* params) { | | 383 | + if (!value->IsUint32()) { | | 384 | + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); | | 385 | + return false; | | 386 | + } | | 387 | + params->length = value.As<Uint32>()->Value(); | | 388 | + if (params->length > 128) { | | 389 | + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); | | 390 | + return false; | 420 | 391 | } | 421 | 392 | return true; | 422 | 393 | } | @@ -451,8 +422,7 @@ AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept | 451 | 422 | cipher(other.cipher), | 452 | 423 | length(other.length), | 453 | 424 | iv(std::move(other.iv)), | 454 | | - additional_data(std::move(other.additional_data)), | 455 | | - tag(std::move(other.tag)) {} | | 425 | + additional_data(std::move(other.additional_data)) {} | 456 | 426 |
| 457 | 427 | AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept { | 458 | 428 | if (&other == this) return *this; | @@ -466,7 +436,6 @@ void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const { | 466 | 436 | if (mode == kCryptoJobAsync) { | 467 | 437 | tracker->TrackFieldWithSize("iv", iv.size()); | 468 | 438 | tracker->TrackFieldWithSize("additional_data", additional_data.size()); | 469 | | - tracker->TrackFieldWithSize("tag", tag.size()); | 470 | 439 | } | 471 | 440 | } | 472 | 441 |
| @@ -510,7 +479,7 @@ Maybe<void> AESCipherTraits::AdditionalConfig( | 510 | 479 | return Nothing<void>(); | 511 | 480 | } | 512 | 481 | } else if (params->cipher.isGcmMode() || params->cipher.isOcbMode()) { | 513 | | - if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || | | 482 | + if (!ValidateAuthTag(env, args[offset + 2], params) || | 514 | 483 | !ValidateAdditionalData(env, mode, args[offset + 3], params)) { | 515 | 484 | return Nothing<void>(); | 516 | 485 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_aes.h -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -52,7 +52,6 @@ struct AESCipherConfig final : public MemoryRetainer { | 52 | 52 | size_t length; | 53 | 53 | ByteSource iv; // Used for both iv or counter | 54 | 54 | ByteSource additional_data; | 55 | | - ByteSource tag; // Used only for authenticated modes (GCM) | 56 | 55 |
| 57 | 56 | AESCipherConfig() = default; | 58 | 57 |
| |
|