Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 6 additions & 58 deletions lib/internal/crypto/aes.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
'use strict';

const {
ArrayBufferIsView,
ArrayBufferPrototypeSlice,
ArrayFrom,
ArrayPrototypePush,
SafeSet,
TypedArrayPrototypeSlice,
} = primordials;

const {
Expand All @@ -28,8 +25,6 @@ const {
kKeyVariantAES_GCM_256,
kKeyVariantAES_KW_256,
kKeyVariantAES_OCB_256,
kWebCryptoCipherDecrypt,
kWebCryptoCipherEncrypt,
} = internalBinding('crypto');

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

async function asyncAesGcmCipher(mode, key, data, algorithm) {
function asyncAesGcmCipher(mode, key, data, algorithm) {
const { tagLength = 128 } = algorithm;

const tagByteLength = tagLength / 8;
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt: {
const slice = ArrayBufferIsView(data) ?
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
tag = slice(data, -tagByteLength);

// Refs: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-operations
//
// > If *plaintext* has a length less than *tagLength* bits, then `throw`
// > an `OperationError`.
if (tagByteLength > tag.byteLength) {
throw lazyDOMException(
'The provided data is too small.',
'OperationError');
}

data = slice(data, 0, -tagByteLength);
break;
}
case kWebCryptoCipherEncrypt:
tag = tagByteLength;
break;
}

return await jobPromise(() => new AESCipherJob(
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-GCM', key[kAlgorithm].length),
algorithm.iv,
tag,
tagByteLength,
algorithm.additionalData));
}

async function asyncAesOcbCipher(mode, key, data, algorithm) {
function asyncAesOcbCipher(mode, key, data, algorithm) {
const { tagLength = 128 } = algorithm;

const tagByteLength = tagLength / 8;
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt: {
const slice = ArrayBufferIsView(data) ?
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
tag = slice(data, -tagByteLength);

// Similar to GCM, OCB requires the tag to be present for decryption
if (tagByteLength > tag.byteLength) {
throw lazyDOMException(
'The provided data is too small.',
'OperationError');
}

data = slice(data, 0, -tagByteLength);
break;
}
case kWebCryptoCipherEncrypt:
tag = tagByteLength;
break;
}

return await jobPromise(() => new AESCipherJob(
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-OCB', key.algorithm.length),
algorithm.iv,
tag,
tagByteLength,
algorithm.additionalData));
}

Expand Down
31 changes: 2 additions & 29 deletions lib/internal/crypto/chacha20_poly1305.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
'use strict';

const {
ArrayBufferIsView,
ArrayBufferPrototypeSlice,
ArrayFrom,
SafeSet,
TypedArrayPrototypeSlice,
} = primordials;

const {
ChaCha20Poly1305CipherJob,
KeyObjectHandle,
kCryptoJobAsync,
kWebCryptoCipherDecrypt,
kWebCryptoCipherEncrypt,
} = internalBinding('crypto');

const {
Expand Down Expand Up @@ -46,35 +41,13 @@ function validateKeyLength(length) {
throw lazyDOMException('Invalid key length', 'DataError');
}

async function c20pCipher(mode, key, data, algorithm) {
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt: {
const slice = ArrayBufferIsView(data) ?
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;

if (data.byteLength < 16) {
throw lazyDOMException(
'The provided data is too small.',
'OperationError');
}

tag = slice(data, -16);
data = slice(data, 0, -16);
break;
}
case kWebCryptoCipherEncrypt:
tag = 16;
break;
}

return await jobPromise(() => new ChaCha20Poly1305CipherJob(
function c20pCipher(mode, key, data, algorithm) {
return jobPromise(() => new ChaCha20Poly1305CipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
algorithm.iv,
tag,
algorithm.additionalData));
}

Expand Down
85 changes: 27 additions & 58 deletions src/crypto/crypto_aes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,39 +76,35 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
}

size_t tag_len = 0;
size_t data_len = in.size();

if (params.cipher.isGcmMode() || params.cipher.isOcbMode()) {
tag_len = params.length;
switch (cipher_mode) {
case kWebCryptoCipherDecrypt: {
// If in decrypt mode, the auth tag must be set in the params.tag.
CHECK(params.tag);
// In decrypt mode, the auth tag is appended to the end of the
// ciphertext. Split it off and set it on the cipher context.
if (data_len < tag_len) {
return WebCryptoCipherStatus::FAILED;
}
data_len -= tag_len;

// For OCB mode, we need to set the auth tag length before setting the
// tag
if (params.cipher.isOcbMode()) {
if (!ctx.setAeadTagLength(params.tag.size())) {
if (!ctx.setAeadTagLength(tag_len)) {
return WebCryptoCipherStatus::FAILED;
}
}

ncrypto::Buffer<const char> buffer = {
.data = params.tag.data<char>(),
.len = params.tag.size(),
.data = in.data<char>() + data_len,
.len = tag_len,
};
if (!ctx.setAeadTag(buffer)) {
return WebCryptoCipherStatus::FAILED;
}
break;
}
case kWebCryptoCipherEncrypt: {
// In encrypt mode, we grab the tag length here. We'll use it to
// ensure that that allocated buffer has enough room for both the
// final block and the auth tag. Unlike our other AES-GCM implementation
// in CipherBase, in WebCrypto, the auth tag is concatenated to the end
// of the generated ciphertext and returned in the same ArrayBuffer.
tag_len = params.length;

// For OCB mode, we need to set the auth tag length
if (params.cipher.isOcbMode()) {
if (!ctx.setAeadTagLength(tag_len)) {
return WebCryptoCipherStatus::FAILED;
Expand All @@ -122,7 +118,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
}

size_t total = 0;
int buf_len = in.size() + ctx.getBlockSize() + tag_len;
int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0);
int out_len;

ncrypto::Buffer<const unsigned char> buffer = {
Expand All @@ -148,9 +144,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
// Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
buffer = {
.data = in.data<unsigned char>(),
.len = in.size(),
.len = data_len,
};
if (in.empty()) {
if (data_len == 0) {
out_len = 0;
} else if (!ctx.update(buffer, ptr, &out_len)) {
return WebCryptoCipherStatus::FAILED;
Expand Down Expand Up @@ -381,42 +377,17 @@ bool ValidateCounter(
return true;
}

bool ValidateAuthTag(
Environment* env,
CryptoJobMode mode,
WebCryptoCipherMode cipher_mode,
Local<Value> value,
AESCipherConfig* params) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt: {
if (!IsAnyBufferSource(value)) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
ArrayBufferOrViewContents<char> tag_contents(value);
if (!tag_contents.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
return false;
}
params->tag = mode == kCryptoJobAsync
? tag_contents.ToCopy()
: tag_contents.ToByteSource();
break;
}
case kWebCryptoCipherEncrypt: {
if (!value->IsUint32()) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
params->length = value.As<Uint32>()->Value();
if (params->length > 128) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
break;
}
default:
UNREACHABLE();
bool ValidateAuthTag(Environment* env,
Local<Value> value,
AESCipherConfig* params) {
if (!value->IsUint32()) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
params->length = value.As<Uint32>()->Value();
if (params->length > 128) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
return true;
}
Expand Down Expand Up @@ -451,8 +422,7 @@ AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
cipher(other.cipher),
length(other.length),
iv(std::move(other.iv)),
additional_data(std::move(other.additional_data)),
tag(std::move(other.tag)) {}
additional_data(std::move(other.additional_data)) {}

AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
if (&other == this) return *this;
Expand All @@ -466,7 +436,6 @@ void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("iv", iv.size());
tracker->TrackFieldWithSize("additional_data", additional_data.size());
tracker->TrackFieldWithSize("tag", tag.size());
}
}

Expand Down Expand Up @@ -510,7 +479,7 @@ Maybe<void> AESCipherTraits::AdditionalConfig(
return Nothing<void>();
}
} else if (params->cipher.isGcmMode() || params->cipher.isOcbMode()) {
if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
if (!ValidateAuthTag(env, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<void>();
}
Expand Down
1 change: 0 additions & 1 deletion src/crypto/crypto_aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ struct AESCipherConfig final : public MemoryRetainer {
size_t length;
ByteSource iv; // Used for both iv or counter
ByteSource additional_data;
ByteSource tag; // Used only for authenticated modes (GCM)

AESCipherConfig() = default;

Expand Down
Loading
Loading