Skip to content

Commit

Permalink
crypto: allow to restrict valid GCM tag length
Browse files Browse the repository at this point in the history
This change allows users to restrict accepted GCM authentication tag
lengths to a single value.

Backport-PR-URL: #20706
PR-URL: #20039
Fixes: #17523
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yihong Wang <yh.wang@ibm.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
  • Loading branch information
tniessen authored and addaleax committed May 14, 2018
1 parent b6ea5df commit bdd2856
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 7 deletions.
8 changes: 7 additions & 1 deletion doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,10 @@ to create the `Decipher` object.
<!-- YAML
added: v0.1.94
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/20039
description: The `authTagLength` option can now be used to restrict accepted
GCM authentication tag lengths.
- version: v9.9.0
pr-url: https://github.com/nodejs/node/pull/18644
description: The `iv` parameter may now be `null` for ciphers which do not
Expand All @@ -1471,7 +1475,9 @@ and initialization vector (`iv`).
The `options` argument controls stream behavior and is optional except when a
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
`authTagLength` option is required and specifies the length of the
authentication tag in bytes, see [CCM mode][].
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
option is not required but can be used to restrict accepted authentication tags
to those with the specified length.

The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
recent OpenSSL releases, `openssl list -cipher-algorithms`
Expand Down
38 changes: 33 additions & 5 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,10 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
}


static bool IsValidGCMTagLength(unsigned int tag_len) {
return tag_len == 4 || tag_len == 8 || tag_len >= 12 && tag_len <= 16;
}

bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
int auth_tag_len) {
CHECK(IsAuthenticatedMode());
Expand All @@ -2799,7 +2803,8 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
return false;
}

if (EVP_CIPHER_CTX_mode(ctx_) == EVP_CIPH_CCM_MODE) {
const int mode = EVP_CIPHER_CTX_mode(ctx_);
if (mode == EVP_CIPH_CCM_MODE) {
if (auth_tag_len < 0) {
char msg[128];
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
Expand Down Expand Up @@ -2832,6 +2837,21 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
} else {
max_message_size_ = INT_MAX;
}
} else {
CHECK_EQ(mode, EVP_CIPH_GCM_MODE);

if (auth_tag_len >= 0) {
if (!IsValidGCMTagLength(auth_tag_len)) {
char msg[50];
snprintf(msg, sizeof(msg),
"Invalid GCM authentication tag length: %u", auth_tag_len);
env()->ThrowError(msg);
return false;
}

// Remember the given authentication tag length for later.
auth_tag_len_ = auth_tag_len;
}
}

return true;
Expand Down Expand Up @@ -2867,7 +2887,7 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
// Only callable after Final and if encrypting.
if (cipher->ctx_ != nullptr ||
cipher->kind_ != kCipher ||
cipher->auth_tag_len_ == 0) {
cipher->auth_tag_len_ == kNoAuthTagLength) {
return args.GetReturnValue().SetUndefined();
}

Expand All @@ -2892,7 +2912,14 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
unsigned int tag_len = Buffer::Length(args[0]);
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_);
if (mode == EVP_CIPH_GCM_MODE) {
if (tag_len > 16 || (tag_len < 12 && tag_len != 8 && tag_len != 4)) {
if (cipher->auth_tag_len_ != kNoAuthTagLength &&
cipher->auth_tag_len_ != tag_len) {
char msg[50];
snprintf(msg, sizeof(msg),
"Invalid GCM authentication tag length: %u", tag_len);
return cipher->env()->ThrowError(msg);
}
if (!IsValidGCMTagLength(tag_len)) {
char msg[125];
snprintf(msg, sizeof(msg),
"Permitting authentication tag lengths of %u bytes is deprecated. "
Expand Down Expand Up @@ -2929,7 +2956,8 @@ 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) {
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0 &&
auth_tag_len_ != kNoAuthTagLength) {
if (!EVP_CIPHER_CTX_ctrl(ctx_,
EVP_CTRL_CCM_SET_TAG,
auth_tag_len_,
Expand Down Expand Up @@ -2982,7 +3010,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,

// on first update:
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
!auth_tag_set_) {
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
EVP_CIPHER_CTX_ctrl(ctx_,
EVP_CTRL_GCM_SET_TAG,
auth_tag_len_,
Expand Down
3 changes: 2 additions & 1 deletion src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ class CipherBase : public BaseObject {
kErrorMessageSize,
kErrorState
};
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);

void Init(const char* cipher_type,
const char* key_buf,
Expand Down Expand Up @@ -399,7 +400,7 @@ class CipherBase : public BaseObject {
ctx_(nullptr),
kind_(kind),
auth_tag_set_(false),
auth_tag_len_(0),
auth_tag_len_(kNoAuthTagLength),
pending_auth_failed_(false) {
MakeWeak();
}
Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-crypto-authenticated.js
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,46 @@ for (const test of TEST_CASES) {
'qkuZpJWCewa6Szih');
decrypt.setAuthTag(Buffer.from('1'.repeat(length)));
}

// Explicitely passing invalid lengths should throw.
for (const length of [0, 1, 2, 6, 9, 10, 11, 17]) {
common.expectsError(() => {
crypto.createDecipheriv('aes-256-gcm',
'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
'qkuZpJWCewa6Szih',
{
authTagLength: length
});
}, {
type: Error,
message: `Invalid GCM authentication tag length: ${length}`
});
}
}

// Test that users can manually restrict the GCM tag length to a single value.
{
const decipher = crypto.createDecipheriv('aes-256-gcm',
'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
'qkuZpJWCewa6Szih', {
authTagLength: 8
});

common.expectsError(() => {
// This tag would normally be allowed.
decipher.setAuthTag(Buffer.from('1'.repeat(12)));
}, {
type: Error,
message: 'Invalid GCM authentication tag length: 12'
});

// The Decipher object should be left intact.
decipher.setAuthTag(Buffer.from('445352d3ff85cf94', 'hex'));
const text = Buffer.concat([
decipher.update('3a2a3647', 'hex'),
decipher.final()
]);
assert.strictEqual(text.toString('utf8'), 'node');
}

// Test that create(De|C)ipher(iv)? throws if the mode is CCM and an invalid
Expand Down

0 comments on commit bdd2856

Please sign in to comment.