Skip to content

Commit c47c79e

Browse files
tniessentargos
authored andcommitted
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: #22421 PR-URL: #22538 Reviewed-By: Anna Henningsen <[email protected]>
1 parent d3740d8 commit c47c79e

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

src/node_crypto.cc

+24-16
Original file line numberDiff line numberDiff line change
@@ -2931,6 +2931,20 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
29312931
}
29322932

29332933

2934+
bool CipherBase::MaybePassAuthTagToOpenSSL() {
2935+
if (!auth_tag_set_ && auth_tag_len_ != kNoAuthTagLength) {
2936+
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
2937+
EVP_CTRL_AEAD_SET_TAG,
2938+
auth_tag_len_,
2939+
reinterpret_cast<unsigned char*>(auth_tag_))) {
2940+
return false;
2941+
}
2942+
auth_tag_set_ = true;
2943+
}
2944+
return true;
2945+
}
2946+
2947+
29342948
bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
29352949
if (!ctx_ || !IsAuthenticatedMode())
29362950
return false;
@@ -2950,15 +2964,9 @@ bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
29502964
if (!CheckCCMMessageLength(plaintext_len))
29512965
return false;
29522966

2953-
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0 &&
2954-
auth_tag_len_ != kNoAuthTagLength) {
2955-
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
2956-
EVP_CTRL_CCM_SET_TAG,
2957-
auth_tag_len_,
2958-
reinterpret_cast<unsigned char*>(auth_tag_))) {
2967+
if (kind_ == kDecipher) {
2968+
if (!MaybePassAuthTagToOpenSSL())
29592969
return false;
2960-
}
2961-
auth_tag_set_ = true;
29622970
}
29632971

29642972
// Specify the plaintext length.
@@ -3003,14 +3011,10 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
30033011
return kErrorMessageSize;
30043012
}
30053013

3006-
// on first update:
3007-
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
3008-
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
3009-
CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(),
3010-
EVP_CTRL_AEAD_SET_TAG,
3011-
auth_tag_len_,
3012-
reinterpret_cast<unsigned char*>(auth_tag_)));
3013-
auth_tag_set_ = true;
3014+
// Pass the authentication tag to OpenSSL if possible. This will only happen
3015+
// once, usually on the first update.
3016+
if (kind_ == kDecipher && IsAuthenticatedMode()) {
3017+
CHECK(MaybePassAuthTagToOpenSSL());
30143018
}
30153019

30163020
*out_len = 0;
@@ -3110,6 +3114,10 @@ bool CipherBase::Final(unsigned char** out, int* out_len) {
31103114
*out = Malloc<unsigned char>(
31113115
static_cast<size_t>(EVP_CIPHER_CTX_block_size(ctx_.get())));
31123116

3117+
if (kind_ == kDecipher && IsSupportedAuthenticatedMode(mode)) {
3118+
MaybePassAuthTagToOpenSSL();
3119+
}
3120+
31133121
// In CCM mode, final() only checks whether authentication failed in update().
31143122
// EVP_CipherFinal_ex must not be called and will fail.
31153123
bool ok;

src/node_crypto.h

+1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ class CipherBase : public BaseObject {
385385

386386
bool IsAuthenticatedMode() const;
387387
bool SetAAD(const char* data, unsigned int len, int plaintext_len);
388+
bool MaybePassAuthTagToOpenSSL();
388389

389390
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
390391
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);

test/parallel/test-crypto-authenticated.js

+26
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,29 @@ for (const test of TEST_CASES) {
577577
encrypt.update('boom'); // Should not throw 'Message exceeds maximum size'.
578578
encrypt.final();
579579
}
580+
581+
// Test that the authentication tag can be set at any point before calling
582+
// final() in GCM mode.
583+
{
584+
const plain = Buffer.from('Hello world', 'utf8');
585+
const key = Buffer.from('0123456789abcdef', 'utf8');
586+
const iv = Buffer.from('0123456789ab', 'utf8');
587+
588+
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
589+
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
590+
const authTag = cipher.getAuthTag();
591+
592+
for (const authTagBeforeUpdate of [true, false]) {
593+
const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
594+
if (authTagBeforeUpdate) {
595+
decipher.setAuthTag(authTag);
596+
}
597+
const resultUpdate = decipher.update(ciphertext);
598+
if (!authTagBeforeUpdate) {
599+
decipher.setAuthTag(authTag);
600+
}
601+
const resultFinal = decipher.final();
602+
const result = Buffer.concat([resultUpdate, resultFinal]);
603+
assert(result.equals(plain));
604+
}
605+
}

0 commit comments

Comments
 (0)