Skip to content

Commit

Permalink
crypto: fix edge case in authenticated encryption
Browse files Browse the repository at this point in the history
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: #22828
Reviewed-By: Daniel Bevenius <[email protected]>
  • Loading branch information
tniessen authored and targos committed Sep 20, 2018
1 parent 329ac60 commit 624e516
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 20 deletions.
9 changes: 7 additions & 2 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2891,6 +2891,10 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
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());
if (mode == EVP_CIPH_GCM_MODE) {
Expand Down Expand Up @@ -2923,6 +2927,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {

// Note: we don't use std::min() here to work around a header conflict.
cipher->auth_tag_len_ = tag_len;
cipher->auth_tag_state_ = kAuthTagKnown;
if (cipher->auth_tag_len_ > sizeof(cipher->auth_tag_))
cipher->auth_tag_len_ = sizeof(cipher->auth_tag_);

Expand All @@ -2934,14 +2939,14 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {


bool CipherBase::MaybePassAuthTagToOpenSSL() {
if (!auth_tag_set_ && auth_tag_len_ != kNoAuthTagLength) {
if (auth_tag_state_ == kAuthTagKnown) {
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;
auth_tag_state_ = kAuthTagPassedToOpenSSL;
}
return true;
}
Expand Down
9 changes: 7 additions & 2 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ class CipherBase : public BaseObject {
kErrorMessageSize,
kErrorState
};
enum AuthTagState {
kAuthTagUnknown,
kAuthTagKnown,
kAuthTagPassedToOpenSSL
};
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);

void Init(const char* cipher_type,
Expand Down Expand Up @@ -404,7 +409,7 @@ class CipherBase : public BaseObject {
: BaseObject(env, wrap),
ctx_(nullptr),
kind_(kind),
auth_tag_set_(false),
auth_tag_state_(kAuthTagUnknown),
auth_tag_len_(kNoAuthTagLength),
pending_auth_failed_(false) {
MakeWeak();
Expand All @@ -413,7 +418,7 @@ class CipherBase : public BaseObject {
private:
DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
const CipherKind kind_;
bool auth_tag_set_;
AuthTagState auth_tag_state_;
unsigned int auth_tag_len_;
char auth_tag_[EVP_GCM_TLS_TAG_LEN];
bool pending_auth_failed_;
Expand Down
40 changes: 24 additions & 16 deletions test/parallel/test-crypto-authenticated.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,27 +579,35 @@ for (const test of TEST_CASES) {
}

// 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 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);
for (const mode of ['gcm', 'ocb']) {
for (const authTagLength of mode === 'gcm' ? [undefined, 8] : [8]) {
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]) {
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, {
authTagLength
});
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 resultFinal = decipher.final();
const result = Buffer.concat([resultUpdate, resultFinal]);
assert(result.equals(plain));
}
}

0 comments on commit 624e516

Please sign in to comment.