crypto: fix edge case in authenticated encryption
Restricting the authentication tag length and calling update or setAAD before setAuthTag caused an incorrect authentication tag to be passed to OpenSSL: The auth_tag_len_ field was already set, so the implementation assumed that the tag itself was known as well. PR-URL: https://github.com/nodejs/node/pull/22828 Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
This commit is contained in:
parent
47a0d041d1
commit
a9e7369b11
@ -2897,6 +2897,10 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|||||||
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;
|
||||||
@ -2921,6 +2925,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cipher->auth_tag_len_ = tag_len;
|
cipher->auth_tag_len_ = tag_len;
|
||||||
|
cipher->auth_tag_state_ = kAuthTagKnown;
|
||||||
CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_));
|
CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_));
|
||||||
|
|
||||||
memset(cipher->auth_tag_, 0, sizeof(cipher->auth_tag_));
|
memset(cipher->auth_tag_, 0, sizeof(cipher->auth_tag_));
|
||||||
@ -2931,14 +2936,14 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
|
|
||||||
bool CipherBase::MaybePassAuthTagToOpenSSL() {
|
bool CipherBase::MaybePassAuthTagToOpenSSL() {
|
||||||
if (!auth_tag_set_ && auth_tag_len_ != kNoAuthTagLength) {
|
if (auth_tag_state_ == kAuthTagKnown) {
|
||||||
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
||||||
EVP_CTRL_AEAD_SET_TAG,
|
EVP_CTRL_AEAD_SET_TAG,
|
||||||
auth_tag_len_,
|
auth_tag_len_,
|
||||||
reinterpret_cast<unsigned char*>(auth_tag_))) {
|
reinterpret_cast<unsigned char*>(auth_tag_))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auth_tag_set_ = true;
|
auth_tag_state_ = kAuthTagPassedToOpenSSL;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -363,6 +363,11 @@ class CipherBase : public BaseObject {
|
|||||||
kErrorMessageSize,
|
kErrorMessageSize,
|
||||||
kErrorState
|
kErrorState
|
||||||
};
|
};
|
||||||
|
enum AuthTagState {
|
||||||
|
kAuthTagUnknown,
|
||||||
|
kAuthTagKnown,
|
||||||
|
kAuthTagPassedToOpenSSL
|
||||||
|
};
|
||||||
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
|
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
|
||||||
|
|
||||||
void Init(const char* cipher_type,
|
void Init(const char* cipher_type,
|
||||||
@ -404,7 +409,7 @@ class CipherBase : public BaseObject {
|
|||||||
: BaseObject(env, wrap),
|
: BaseObject(env, wrap),
|
||||||
ctx_(nullptr),
|
ctx_(nullptr),
|
||||||
kind_(kind),
|
kind_(kind),
|
||||||
auth_tag_set_(false),
|
auth_tag_state_(kAuthTagUnknown),
|
||||||
auth_tag_len_(kNoAuthTagLength),
|
auth_tag_len_(kNoAuthTagLength),
|
||||||
pending_auth_failed_(false) {
|
pending_auth_failed_(false) {
|
||||||
MakeWeak();
|
MakeWeak();
|
||||||
@ -413,7 +418,7 @@ class CipherBase : public BaseObject {
|
|||||||
private:
|
private:
|
||||||
DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
|
DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
|
||||||
const CipherKind kind_;
|
const CipherKind kind_;
|
||||||
bool auth_tag_set_;
|
AuthTagState auth_tag_state_;
|
||||||
unsigned int auth_tag_len_;
|
unsigned int auth_tag_len_;
|
||||||
char auth_tag_[EVP_GCM_TLS_TAG_LEN];
|
char auth_tag_[EVP_GCM_TLS_TAG_LEN];
|
||||||
bool pending_auth_failed_;
|
bool pending_auth_failed_;
|
||||||
|
@ -557,27 +557,35 @@ for (const test of TEST_CASES) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test that the authentication tag can be set at any point before calling
|
// Test that the authentication tag can be set at any point before calling
|
||||||
// final() in GCM mode.
|
// final() in GCM or OCB mode.
|
||||||
{
|
{
|
||||||
const plain = Buffer.from('Hello world', 'utf8');
|
const plain = Buffer.from('Hello world', 'utf8');
|
||||||
const key = Buffer.from('0123456789abcdef', 'utf8');
|
const key = Buffer.from('0123456789abcdef', 'utf8');
|
||||||
const iv = Buffer.from('0123456789ab', 'utf8');
|
const iv = Buffer.from('0123456789ab', 'utf8');
|
||||||
|
|
||||||
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
|
for (const mode of ['gcm', 'ocb']) {
|
||||||
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
|
for (const authTagLength of mode === 'gcm' ? [undefined, 8] : [8]) {
|
||||||
const authTag = cipher.getAuthTag();
|
const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, {
|
||||||
|
authTagLength
|
||||||
|
});
|
||||||
|
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
|
||||||
|
const authTag = cipher.getAuthTag();
|
||||||
|
|
||||||
for (const authTagBeforeUpdate of [true, false]) {
|
for (const authTagBeforeUpdate of [true, false]) {
|
||||||
const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
|
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, {
|
||||||
if (authTagBeforeUpdate) {
|
authTagLength
|
||||||
decipher.setAuthTag(authTag);
|
});
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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