Skip to content

Commit 058c5b8

Browse files
committed
crypto: do not allow multiple calls to setAuthTag
Calling setAuthTag multiple times can result in hard to detect bugs since to the user, it is unclear which invocation actually affected OpenSSL. PR-URL: #22931 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com>
1 parent 56493bf commit 058c5b8

File tree

3 files changed

+29
-6
lines changed

3 files changed

+29
-6
lines changed

doc/api/crypto.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ is invalid according to [NIST SP 800-38D][] or does not match the value of the
445445
`authTagLength` option, `decipher.setAuthTag()` will throw an error.
446446

447447
The `decipher.setAuthTag()` method must be called before
448-
[`decipher.final()`][].
448+
[`decipher.final()`][] and can only be called once.
449449

450450
### decipher.setAutoPadding([autoPadding])
451451
<!-- YAML

src/node_crypto.cc

+2-5
Original file line numberDiff line numberDiff line change
@@ -2894,14 +2894,11 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
28942894

28952895
if (!cipher->ctx_ ||
28962896
!cipher->IsAuthenticatedMode() ||
2897-
cipher->kind_ != kDecipher) {
2897+
cipher->kind_ != kDecipher ||
2898+
cipher->auth_tag_state_ != kAuthTagUnknown) {
28982899
return args.GetReturnValue().Set(false);
28992900
}
29002901

2901-
// TODO(tniessen): Throw if the authentication tag has already been set.
2902-
if (cipher->auth_tag_state_ == kAuthTagPassedToOpenSSL)
2903-
return args.GetReturnValue().Set(true);
2904-
29052902
unsigned int tag_len = Buffer::Length(args[0]);
29062903
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
29072904
bool is_valid;

test/parallel/test-crypto-authenticated.js

+26
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,29 @@ for (const test of TEST_CASES) {
589589
}
590590
}
591591
}
592+
593+
// Test that setAuthTag can only be called once.
594+
{
595+
const plain = Buffer.from('Hello world', 'utf8');
596+
const key = Buffer.from('0123456789abcdef', 'utf8');
597+
const iv = Buffer.from('0123456789ab', 'utf8');
598+
const opts = { authTagLength: 8 };
599+
600+
for (const mode of ['gcm', 'ccm', 'ocb']) {
601+
const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, opts);
602+
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
603+
const tag = cipher.getAuthTag();
604+
605+
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts);
606+
decipher.setAuthTag(tag);
607+
assert.throws(() => {
608+
decipher.setAuthTag(tag);
609+
}, errMessages.state);
610+
// Decryption should still work.
611+
const plaintext = Buffer.concat([
612+
decipher.update(ciphertext),
613+
decipher.final()
614+
]);
615+
assert(plain.equals(plaintext));
616+
}
617+
}

0 commit comments

Comments
 (0)