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: https://github.com/nodejs/node/pull/22931 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com>
This commit is contained in:
parent
56493bf1eb
commit
058c5b81cd
@ -445,7 +445,7 @@ is invalid according to [NIST SP 800-38D][] or does not match the value of the
|
|||||||
`authTagLength` option, `decipher.setAuthTag()` will throw an error.
|
`authTagLength` option, `decipher.setAuthTag()` will throw an error.
|
||||||
|
|
||||||
The `decipher.setAuthTag()` method must be called before
|
The `decipher.setAuthTag()` method must be called before
|
||||||
[`decipher.final()`][].
|
[`decipher.final()`][] and can only be called once.
|
||||||
|
|
||||||
### decipher.setAutoPadding([autoPadding])
|
### decipher.setAutoPadding([autoPadding])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
@ -2894,14 +2894,11 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
if (!cipher->ctx_ ||
|
if (!cipher->ctx_ ||
|
||||||
!cipher->IsAuthenticatedMode() ||
|
!cipher->IsAuthenticatedMode() ||
|
||||||
cipher->kind_ != kDecipher) {
|
cipher->kind_ != kDecipher ||
|
||||||
|
cipher->auth_tag_state_ != kAuthTagUnknown) {
|
||||||
return args.GetReturnValue().Set(false);
|
return args.GetReturnValue().Set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(tniessen): Throw if the authentication tag has already been set.
|
|
||||||
if (cipher->auth_tag_state_ == kAuthTagPassedToOpenSSL)
|
|
||||||
return args.GetReturnValue().Set(true);
|
|
||||||
|
|
||||||
unsigned int tag_len = Buffer::Length(args[0]);
|
unsigned int tag_len = Buffer::Length(args[0]);
|
||||||
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
|
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
|
||||||
bool is_valid;
|
bool is_valid;
|
||||||
|
@ -589,3 +589,29 @@ for (const test of TEST_CASES) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that setAuthTag can only be called once.
|
||||||
|
{
|
||||||
|
const plain = Buffer.from('Hello world', 'utf8');
|
||||||
|
const key = Buffer.from('0123456789abcdef', 'utf8');
|
||||||
|
const iv = Buffer.from('0123456789ab', 'utf8');
|
||||||
|
const opts = { authTagLength: 8 };
|
||||||
|
|
||||||
|
for (const mode of ['gcm', 'ccm', 'ocb']) {
|
||||||
|
const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, opts);
|
||||||
|
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
|
||||||
|
const tag = cipher.getAuthTag();
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts);
|
||||||
|
decipher.setAuthTag(tag);
|
||||||
|
assert.throws(() => {
|
||||||
|
decipher.setAuthTag(tag);
|
||||||
|
}, errMessages.state);
|
||||||
|
// Decryption should still work.
|
||||||
|
const plaintext = Buffer.concat([
|
||||||
|
decipher.update(ciphertext),
|
||||||
|
decipher.final()
|
||||||
|
]);
|
||||||
|
assert(plain.equals(plaintext));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user