crypto: improve setAuthTag
This is an attempt to make the behavior of setAuthTag match the documentation: In GCM mode, it can be called at any time before invoking final, even after the last call to update. Fixes: https://github.com/nodejs/node/issues/22421 PR-URL: https://github.com/nodejs/node/pull/22538 Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
594dd4242b
commit
b4026099c3
@ -2928,6 +2928,20 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
|
||||
bool CipherBase::MaybePassAuthTagToOpenSSL() {
|
||||
if (!auth_tag_set_ && auth_tag_len_ != kNoAuthTagLength) {
|
||||
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
||||
EVP_CTRL_AEAD_SET_TAG,
|
||||
auth_tag_len_,
|
||||
reinterpret_cast<unsigned char*>(auth_tag_))) {
|
||||
return false;
|
||||
}
|
||||
auth_tag_set_ = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
|
||||
if (!ctx_ || !IsAuthenticatedMode())
|
||||
return false;
|
||||
@ -2947,15 +2961,9 @@ bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
|
||||
if (!CheckCCMMessageLength(plaintext_len))
|
||||
return false;
|
||||
|
||||
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0 &&
|
||||
auth_tag_len_ != kNoAuthTagLength) {
|
||||
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
||||
EVP_CTRL_CCM_SET_TAG,
|
||||
auth_tag_len_,
|
||||
reinterpret_cast<unsigned char*>(auth_tag_))) {
|
||||
if (kind_ == kDecipher) {
|
||||
if (!MaybePassAuthTagToOpenSSL())
|
||||
return false;
|
||||
}
|
||||
auth_tag_set_ = true;
|
||||
}
|
||||
|
||||
// Specify the plaintext length.
|
||||
@ -3000,14 +3008,10 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
|
||||
return kErrorMessageSize;
|
||||
}
|
||||
|
||||
// on first update:
|
||||
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
|
||||
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
|
||||
CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
||||
EVP_CTRL_AEAD_SET_TAG,
|
||||
auth_tag_len_,
|
||||
reinterpret_cast<unsigned char*>(auth_tag_)));
|
||||
auth_tag_set_ = true;
|
||||
// Pass the authentication tag to OpenSSL if possible. This will only happen
|
||||
// once, usually on the first update.
|
||||
if (kind_ == kDecipher && IsAuthenticatedMode()) {
|
||||
CHECK(MaybePassAuthTagToOpenSSL());
|
||||
}
|
||||
|
||||
*out_len = 0;
|
||||
@ -3107,6 +3111,10 @@ bool CipherBase::Final(unsigned char** out, int* out_len) {
|
||||
*out = Malloc<unsigned char>(
|
||||
static_cast<size_t>(EVP_CIPHER_CTX_block_size(ctx_.get())));
|
||||
|
||||
if (kind_ == kDecipher && IsSupportedAuthenticatedMode(mode)) {
|
||||
MaybePassAuthTagToOpenSSL();
|
||||
}
|
||||
|
||||
// In CCM mode, final() only checks whether authentication failed in update().
|
||||
// EVP_CipherFinal_ex must not be called and will fail.
|
||||
bool ok;
|
||||
|
@ -385,6 +385,7 @@ class CipherBase : public BaseObject {
|
||||
|
||||
bool IsAuthenticatedMode() const;
|
||||
bool SetAAD(const char* data, unsigned int len, int plaintext_len);
|
||||
bool MaybePassAuthTagToOpenSSL();
|
||||
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
@ -555,3 +555,29 @@ for (const test of TEST_CASES) {
|
||||
encrypt.update('boom'); // Should not throw 'Message exceeds maximum size'.
|
||||
encrypt.final();
|
||||
}
|
||||
|
||||
// Test that the authentication tag can be set at any point before calling
|
||||
// final() in GCM mode.
|
||||
{
|
||||
const plain = Buffer.from('Hello world', 'utf8');
|
||||
const key = Buffer.from('0123456789abcdef', 'utf8');
|
||||
const iv = Buffer.from('0123456789ab', 'utf8');
|
||||
|
||||
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
|
||||
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
for (const authTagBeforeUpdate of [true, false]) {
|
||||
const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
|
||||
if (authTagBeforeUpdate) {
|
||||
decipher.setAuthTag(authTag);
|
||||
}
|
||||
const resultUpdate = decipher.update(ciphertext);
|
||||
if (!authTagBeforeUpdate) {
|
||||
decipher.setAuthTag(authTag);
|
||||
}
|
||||
const resultFinal = decipher.final();
|
||||
const result = Buffer.concat([resultUpdate, resultFinal]);
|
||||
assert(result.equals(plain));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user