Skip to content

Commit bdd2856

Browse files
tniessenaddaleax
authored andcommitted
crypto: allow to restrict valid GCM tag length
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 <[email protected]> Reviewed-By: Yihong Wang <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent b6ea5df commit bdd2856

File tree

4 files changed

+82
-7
lines changed

4 files changed

+82
-7
lines changed

doc/api/crypto.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,10 @@ to create the `Decipher` object.
14541454
<!-- YAML
14551455
added: v0.1.94
14561456
changes:
1457+
- version: REPLACEME
1458+
pr-url: https://github.com/nodejs/node/pull/20039
1459+
description: The `authTagLength` option can now be used to restrict accepted
1460+
GCM authentication tag lengths.
14571461
- version: v9.9.0
14581462
pr-url: https://github.com/nodejs/node/pull/18644
14591463
description: The `iv` parameter may now be `null` for ciphers which do not
@@ -1471,7 +1475,9 @@ and initialization vector (`iv`).
14711475
The `options` argument controls stream behavior and is optional except when a
14721476
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
14731477
`authTagLength` option is required and specifies the length of the
1474-
authentication tag in bytes, see [CCM mode][].
1478+
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
1479+
option is not required but can be used to restrict accepted authentication tags
1480+
to those with the specified length.
14751481

14761482
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
14771483
recent OpenSSL releases, `openssl list -cipher-algorithms`

src/node_crypto.cc

+33-5
Original file line numberDiff line numberDiff line change
@@ -2790,6 +2790,10 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
27902790
}
27912791

27922792

2793+
static bool IsValidGCMTagLength(unsigned int tag_len) {
2794+
return tag_len == 4 || tag_len == 8 || tag_len >= 12 && tag_len <= 16;
2795+
}
2796+
27932797
bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
27942798
int auth_tag_len) {
27952799
CHECK(IsAuthenticatedMode());
@@ -2799,7 +2803,8 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
27992803
return false;
28002804
}
28012805

2802-
if (EVP_CIPHER_CTX_mode(ctx_) == EVP_CIPH_CCM_MODE) {
2806+
const int mode = EVP_CIPHER_CTX_mode(ctx_);
2807+
if (mode == EVP_CIPH_CCM_MODE) {
28032808
if (auth_tag_len < 0) {
28042809
char msg[128];
28052810
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
@@ -2832,6 +2837,21 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
28322837
} else {
28332838
max_message_size_ = INT_MAX;
28342839
}
2840+
} else {
2841+
CHECK_EQ(mode, EVP_CIPH_GCM_MODE);
2842+
2843+
if (auth_tag_len >= 0) {
2844+
if (!IsValidGCMTagLength(auth_tag_len)) {
2845+
char msg[50];
2846+
snprintf(msg, sizeof(msg),
2847+
"Invalid GCM authentication tag length: %u", auth_tag_len);
2848+
env()->ThrowError(msg);
2849+
return false;
2850+
}
2851+
2852+
// Remember the given authentication tag length for later.
2853+
auth_tag_len_ = auth_tag_len;
2854+
}
28352855
}
28362856

28372857
return true;
@@ -2867,7 +2887,7 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
28672887
// Only callable after Final and if encrypting.
28682888
if (cipher->ctx_ != nullptr ||
28692889
cipher->kind_ != kCipher ||
2870-
cipher->auth_tag_len_ == 0) {
2890+
cipher->auth_tag_len_ == kNoAuthTagLength) {
28712891
return args.GetReturnValue().SetUndefined();
28722892
}
28732893

@@ -2892,7 +2912,14 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
28922912
unsigned int tag_len = Buffer::Length(args[0]);
28932913
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_);
28942914
if (mode == EVP_CIPH_GCM_MODE) {
2895-
if (tag_len > 16 || (tag_len < 12 && tag_len != 8 && tag_len != 4)) {
2915+
if (cipher->auth_tag_len_ != kNoAuthTagLength &&
2916+
cipher->auth_tag_len_ != tag_len) {
2917+
char msg[50];
2918+
snprintf(msg, sizeof(msg),
2919+
"Invalid GCM authentication tag length: %u", tag_len);
2920+
return cipher->env()->ThrowError(msg);
2921+
}
2922+
if (!IsValidGCMTagLength(tag_len)) {
28962923
char msg[125];
28972924
snprintf(msg, sizeof(msg),
28982925
"Permitting authentication tag lengths of %u bytes is deprecated. "
@@ -2929,7 +2956,8 @@ bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
29292956
if (!CheckCCMMessageLength(plaintext_len))
29302957
return false;
29312958

2932-
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0) {
2959+
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0 &&
2960+
auth_tag_len_ != kNoAuthTagLength) {
29332961
if (!EVP_CIPHER_CTX_ctrl(ctx_,
29342962
EVP_CTRL_CCM_SET_TAG,
29352963
auth_tag_len_,
@@ -2982,7 +3010,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
29823010

29833011
// on first update:
29843012
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
2985-
!auth_tag_set_) {
3013+
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
29863014
EVP_CIPHER_CTX_ctrl(ctx_,
29873015
EVP_CTRL_GCM_SET_TAG,
29883016
auth_tag_len_,

src/node_crypto.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ class CipherBase : public BaseObject {
360360
kErrorMessageSize,
361361
kErrorState
362362
};
363+
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
363364

364365
void Init(const char* cipher_type,
365366
const char* key_buf,
@@ -399,7 +400,7 @@ class CipherBase : public BaseObject {
399400
ctx_(nullptr),
400401
kind_(kind),
401402
auth_tag_set_(false),
402-
auth_tag_len_(0),
403+
auth_tag_len_(kNoAuthTagLength),
403404
pending_auth_failed_(false) {
404405
MakeWeak();
405406
}

test/parallel/test-crypto-authenticated.js

+40
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,46 @@ for (const test of TEST_CASES) {
727727
'qkuZpJWCewa6Szih');
728728
decrypt.setAuthTag(Buffer.from('1'.repeat(length)));
729729
}
730+
731+
// Explicitely passing invalid lengths should throw.
732+
for (const length of [0, 1, 2, 6, 9, 10, 11, 17]) {
733+
common.expectsError(() => {
734+
crypto.createDecipheriv('aes-256-gcm',
735+
'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
736+
'qkuZpJWCewa6Szih',
737+
{
738+
authTagLength: length
739+
});
740+
}, {
741+
type: Error,
742+
message: `Invalid GCM authentication tag length: ${length}`
743+
});
744+
}
745+
}
746+
747+
// Test that users can manually restrict the GCM tag length to a single value.
748+
{
749+
const decipher = crypto.createDecipheriv('aes-256-gcm',
750+
'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
751+
'qkuZpJWCewa6Szih', {
752+
authTagLength: 8
753+
});
754+
755+
common.expectsError(() => {
756+
// This tag would normally be allowed.
757+
decipher.setAuthTag(Buffer.from('1'.repeat(12)));
758+
}, {
759+
type: Error,
760+
message: 'Invalid GCM authentication tag length: 12'
761+
});
762+
763+
// The Decipher object should be left intact.
764+
decipher.setAuthTag(Buffer.from('445352d3ff85cf94', 'hex'));
765+
const text = Buffer.concat([
766+
decipher.update('3a2a3647', 'hex'),
767+
decipher.final()
768+
]);
769+
assert.strictEqual(text.toString('utf8'), 'node');
730770
}
731771

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

0 commit comments

Comments
 (0)