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:
Tobias Nießen 2018-09-18 14:14:50 +02:00
parent 56493bf1eb
commit 058c5b81cd
No known key found for this signature in database
GPG Key ID: 718207F8FD156B70
3 changed files with 29 additions and 6 deletions

View File

@ -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.
The `decipher.setAuthTag()` method must be called before
[`decipher.final()`][].
[`decipher.final()`][] and can only be called once.
### decipher.setAutoPadding([autoPadding])
<!-- YAML

View File

@ -2894,14 +2894,11 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
if (!cipher->ctx_ ||
!cipher->IsAuthenticatedMode() ||
cipher->kind_ != kDecipher) {
cipher->kind_ != kDecipher ||
cipher->auth_tag_state_ != kAuthTagUnknown) {
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]);
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
bool is_valid;

View File

@ -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));
}
}