Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8aa6e70

Browse filesBrowse files
panvaaduh95
authored andcommitted
crypto: refactor WebCrypto AEAD algorithms auth tag handling
PR-URL: #62169 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 20cb932 commit 8aa6e70
Copy full SHA for 8aa6e70

9 files changed

+91-227Lines changed: 91 additions & 227 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/internal/crypto/aes.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/aes.js
+6-58Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
'use strict';
22

33
const {
4-
ArrayBufferIsView,
5-
ArrayBufferPrototypeSlice,
64
ArrayFrom,
75
ArrayPrototypePush,
86
SafeSet,
9-
TypedArrayPrototypeSlice,
107
} = primordials;
118

129
const {
@@ -28,8 +25,6 @@ const {
2825
kKeyVariantAES_GCM_256,
2926
kKeyVariantAES_KW_256,
3027
kKeyVariantAES_OCB_256,
31-
kWebCryptoCipherDecrypt,
32-
kWebCryptoCipherEncrypt,
3328
} = internalBinding('crypto');
3429

3530
const {
@@ -143,80 +138,33 @@ function asyncAesKwCipher(mode, key, data) {
143138
getVariant('AES-KW', key[kAlgorithm].length)));
144139
}
145140

146-
async function asyncAesGcmCipher(mode, key, data, algorithm) {
141+
function asyncAesGcmCipher(mode, key, data, algorithm) {
147142
const { tagLength = 128 } = algorithm;
148-
149143
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-
}
166144

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(
176146
kCryptoJobAsync,
177147
mode,
178148
key[kKeyObject][kHandle],
179149
data,
180150
getVariant('AES-GCM', key[kAlgorithm].length),
181151
algorithm.iv,
182-
tag,
152+
tagByteLength,
183153
algorithm.additionalData));
184154
}
185155

186-
async function asyncAesOcbCipher(mode, key, data, algorithm) {
156+
function asyncAesOcbCipher(mode, key, data, algorithm) {
187157
const { tagLength = 128 } = algorithm;
188-
189158
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-
}
203159

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(
213161
kCryptoJobAsync,
214162
mode,
215163
key[kKeyObject][kHandle],
216164
data,
217165
getVariant('AES-OCB', key.algorithm.length),
218166
algorithm.iv,
219-
tag,
167+
tagByteLength,
220168
algorithm.additionalData));
221169
}
222170

Collapse file

‎lib/internal/crypto/chacha20_poly1305.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/chacha20_poly1305.js
+2-29Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
'use strict';
22

33
const {
4-
ArrayBufferIsView,
5-
ArrayBufferPrototypeSlice,
64
ArrayFrom,
75
SafeSet,
8-
TypedArrayPrototypeSlice,
96
} = primordials;
107

118
const {
129
ChaCha20Poly1305CipherJob,
1310
KeyObjectHandle,
1411
kCryptoJobAsync,
15-
kWebCryptoCipherDecrypt,
16-
kWebCryptoCipherEncrypt,
1712
} = internalBinding('crypto');
1813

1914
const {
@@ -46,35 +41,13 @@ function validateKeyLength(length) {
4641
throw lazyDOMException('Invalid key length', 'DataError');
4742
}
4843

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(
7246
kCryptoJobAsync,
7347
mode,
7448
key[kKeyObject][kHandle],
7549
data,
7650
algorithm.iv,
77-
tag,
7851
algorithm.additionalData));
7952
}
8053

Collapse file

‎src/crypto/crypto_aes.cc‎

Copy file name to clipboardExpand all lines: src/crypto/crypto_aes.cc
+27-58Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -76,39 +76,35 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
7676
}
7777

7878
size_t tag_len = 0;
79+
size_t data_len = in.size();
7980

8081
if (params.cipher.isGcmMode() || params.cipher.isOcbMode()) {
82+
tag_len = params.length;
8183
switch (cipher_mode) {
8284
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;
8591

86-
// For OCB mode, we need to set the auth tag length before setting the
87-
// tag
8892
if (params.cipher.isOcbMode()) {
89-
if (!ctx.setAeadTagLength(params.tag.size())) {
93+
if (!ctx.setAeadTagLength(tag_len)) {
9094
return WebCryptoCipherStatus::FAILED;
9195
}
9296
}
9397

9498
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,
97101
};
98102
if (!ctx.setAeadTag(buffer)) {
99103
return WebCryptoCipherStatus::FAILED;
100104
}
101105
break;
102106
}
103107
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
112108
if (params.cipher.isOcbMode()) {
113109
if (!ctx.setAeadTagLength(tag_len)) {
114110
return WebCryptoCipherStatus::FAILED;
@@ -122,7 +118,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
122118
}
123119

124120
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);
126122
int out_len;
127123

128124
ncrypto::Buffer<const unsigned char> buffer = {
@@ -148,9 +144,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
148144
// Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
149145
buffer = {
150146
.data = in.data<unsigned char>(),
151-
.len = in.size(),
147+
.len = data_len,
152148
};
153-
if (in.empty()) {
149+
if (data_len == 0) {
154150
out_len = 0;
155151
} else if (!ctx.update(buffer, ptr, &out_len)) {
156152
return WebCryptoCipherStatus::FAILED;
@@ -381,42 +377,17 @@ bool ValidateCounter(
381377
return true;
382378
}
383379

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;
420391
}
421392
return true;
422393
}
@@ -451,8 +422,7 @@ AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
451422
cipher(other.cipher),
452423
length(other.length),
453424
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)) {}
456426

457427
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
458428
if (&other == this) return *this;
@@ -466,7 +436,6 @@ void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
466436
if (mode == kCryptoJobAsync) {
467437
tracker->TrackFieldWithSize("iv", iv.size());
468438
tracker->TrackFieldWithSize("additional_data", additional_data.size());
469-
tracker->TrackFieldWithSize("tag", tag.size());
470439
}
471440
}
472441

@@ -510,7 +479,7 @@ Maybe<void> AESCipherTraits::AdditionalConfig(
510479
return Nothing<void>();
511480
}
512481
} 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) ||
514483
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
515484
return Nothing<void>();
516485
}
Collapse file

‎src/crypto/crypto_aes.h‎

Copy file name to clipboardExpand all lines: src/crypto/crypto_aes.h
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ struct AESCipherConfig final : public MemoryRetainer {
5252
size_t length;
5353
ByteSource iv; // Used for both iv or counter
5454
ByteSource additional_data;
55-
ByteSource tag; // Used only for authenticated modes (GCM)
5655

5756
AESCipherConfig() = default;
5857

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.