Skip to content

Commit a9e7369

Browse files
committed
crypto: fix edge case in authenticated encryption
Restricting the authentication tag length and calling update or setAAD before setAuthTag caused an incorrect authentication tag to be passed to OpenSSL: The auth_tag_len_ field was already set, so the implementation assumed that the tag itself was known as well. PR-URL: #22828 Reviewed-By: Daniel Bevenius <[email protected]>
1 parent 47a0d04 commit a9e7369

File tree

3 files changed

+38
-20
lines changed

3 files changed

+38
-20
lines changed

src/node_crypto.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2897,6 +2897,10 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
28972897
return args.GetReturnValue().Set(false);
28982898
}
28992899

2900+
// TODO(tniessen): Throw if the authentication tag has already been set.
2901+
if (cipher->auth_tag_state_ == kAuthTagPassedToOpenSSL)
2902+
return args.GetReturnValue().Set(true);
2903+
29002904
unsigned int tag_len = Buffer::Length(args[0]);
29012905
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
29022906
bool is_valid;
@@ -2921,6 +2925,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
29212925
}
29222926

29232927
cipher->auth_tag_len_ = tag_len;
2928+
cipher->auth_tag_state_ = kAuthTagKnown;
29242929
CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_));
29252930

29262931
memset(cipher->auth_tag_, 0, sizeof(cipher->auth_tag_));
@@ -2931,14 +2936,14 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
29312936

29322937

29332938
bool CipherBase::MaybePassAuthTagToOpenSSL() {
2934-
if (!auth_tag_set_ && auth_tag_len_ != kNoAuthTagLength) {
2939+
if (auth_tag_state_ == kAuthTagKnown) {
29352940
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
29362941
EVP_CTRL_AEAD_SET_TAG,
29372942
auth_tag_len_,
29382943
reinterpret_cast<unsigned char*>(auth_tag_))) {
29392944
return false;
29402945
}
2941-
auth_tag_set_ = true;
2946+
auth_tag_state_ = kAuthTagPassedToOpenSSL;
29422947
}
29432948
return true;
29442949
}

src/node_crypto.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,11 @@ class CipherBase : public BaseObject {
363363
kErrorMessageSize,
364364
kErrorState
365365
};
366+
enum AuthTagState {
367+
kAuthTagUnknown,
368+
kAuthTagKnown,
369+
kAuthTagPassedToOpenSSL
370+
};
366371
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
367372

368373
void Init(const char* cipher_type,
@@ -404,7 +409,7 @@ class CipherBase : public BaseObject {
404409
: BaseObject(env, wrap),
405410
ctx_(nullptr),
406411
kind_(kind),
407-
auth_tag_set_(false),
412+
auth_tag_state_(kAuthTagUnknown),
408413
auth_tag_len_(kNoAuthTagLength),
409414
pending_auth_failed_(false) {
410415
MakeWeak();
@@ -413,7 +418,7 @@ class CipherBase : public BaseObject {
413418
private:
414419
DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
415420
const CipherKind kind_;
416-
bool auth_tag_set_;
421+
AuthTagState auth_tag_state_;
417422
unsigned int auth_tag_len_;
418423
char auth_tag_[EVP_GCM_TLS_TAG_LEN];
419424
bool pending_auth_failed_;

test/parallel/test-crypto-authenticated.js

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -557,27 +557,35 @@ for (const test of TEST_CASES) {
557557
}
558558

559559
// Test that the authentication tag can be set at any point before calling
560-
// final() in GCM mode.
560+
// final() in GCM or OCB mode.
561561
{
562562
const plain = Buffer.from('Hello world', 'utf8');
563563
const key = Buffer.from('0123456789abcdef', 'utf8');
564564
const iv = Buffer.from('0123456789ab', 'utf8');
565565

566-
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
567-
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
568-
const authTag = cipher.getAuthTag();
569-
570-
for (const authTagBeforeUpdate of [true, false]) {
571-
const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
572-
if (authTagBeforeUpdate) {
573-
decipher.setAuthTag(authTag);
574-
}
575-
const resultUpdate = decipher.update(ciphertext);
576-
if (!authTagBeforeUpdate) {
577-
decipher.setAuthTag(authTag);
566+
for (const mode of ['gcm', 'ocb']) {
567+
for (const authTagLength of mode === 'gcm' ? [undefined, 8] : [8]) {
568+
const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, {
569+
authTagLength
570+
});
571+
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
572+
const authTag = cipher.getAuthTag();
573+
574+
for (const authTagBeforeUpdate of [true, false]) {
575+
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, {
576+
authTagLength
577+
});
578+
if (authTagBeforeUpdate) {
579+
decipher.setAuthTag(authTag);
580+
}
581+
const resultUpdate = decipher.update(ciphertext);
582+
if (!authTagBeforeUpdate) {
583+
decipher.setAuthTag(authTag);
584+
}
585+
const resultFinal = decipher.final();
586+
const result = Buffer.concat([resultUpdate, resultFinal]);
587+
assert(result.equals(plain));
588+
}
578589
}
579-
const resultFinal = decipher.final();
580-
const result = Buffer.concat([resultUpdate, resultFinal]);
581-
assert(result.equals(plain));
582590
}
583591
}

0 commit comments

Comments
 (0)