Skip to content

Commit 358d8ff

Browse files
committed
crypto: allow to restrict valid GCM tag length
This change allows users to restrict accepted GCM authentication tag lengths to a single value. 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>
1 parent 854f840 commit 358d8ff

File tree

4 files changed

+74
-7
lines changed

4 files changed

+74
-7
lines changed

doc/api/crypto.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,10 @@ to create the `Decipher` object.
14571457
<!-- YAML
14581458
added: v0.1.94
14591459
changes:
1460+
- version: REPLACEME
1461+
pr-url: https://github.com/nodejs/node/pull/20039
1462+
description: The `authTagLength` option can now be used to restrict accepted
1463+
GCM authentication tag lengths.
14601464
- version: v9.9.0
14611465
pr-url: https://github.com/nodejs/node/pull/18644
14621466
description: The `iv` parameter may now be `null` for ciphers which do not
@@ -1474,7 +1478,9 @@ and initialization vector (`iv`).
14741478
The `options` argument controls stream behavior and is optional except when a
14751479
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
14761480
`authTagLength` option is required and specifies the length of the
1477-
authentication tag in bytes, see [CCM mode][].
1481+
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
1482+
option is not required but can be used to restrict accepted authentication tags
1483+
to those with the specified length.
14781484

14791485
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
14801486
recent OpenSSL releases, `openssl list-cipher-algorithms` will display the

src/node_crypto.cc

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2797,6 +2797,10 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
27972797
}
27982798

27992799

2800+
static bool IsValidGCMTagLength(unsigned int tag_len) {
2801+
return tag_len == 4 || tag_len == 8 || tag_len >= 12 && tag_len <= 16;
2802+
}
2803+
28002804
bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
28012805
int auth_tag_len) {
28022806
CHECK(IsAuthenticatedMode());
@@ -2809,7 +2813,8 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
28092813
return false;
28102814
}
28112815

2812-
if (EVP_CIPHER_CTX_mode(ctx_) == EVP_CIPH_CCM_MODE) {
2816+
const int mode = EVP_CIPHER_CTX_mode(ctx_);
2817+
if (mode == EVP_CIPH_CCM_MODE) {
28132818
if (auth_tag_len < 0) {
28142819
char msg[128];
28152820
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
@@ -2842,6 +2847,21 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
28422847
} else {
28432848
max_message_size_ = INT_MAX;
28442849
}
2850+
} else {
2851+
CHECK_EQ(mode, EVP_CIPH_GCM_MODE);
2852+
2853+
if (auth_tag_len >= 0) {
2854+
if (!IsValidGCMTagLength(auth_tag_len)) {
2855+
char msg[50];
2856+
snprintf(msg, sizeof(msg),
2857+
"Invalid GCM authentication tag length: %u", auth_tag_len);
2858+
env()->ThrowError(msg);
2859+
return false;
2860+
}
2861+
2862+
// Remember the given authentication tag length for later.
2863+
auth_tag_len_ = auth_tag_len;
2864+
}
28452865
}
28462866

28472867
return true;
@@ -2877,7 +2897,7 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
28772897
// Only callable after Final and if encrypting.
28782898
if (cipher->ctx_ != nullptr ||
28792899
cipher->kind_ != kCipher ||
2880-
cipher->auth_tag_len_ == 0) {
2900+
cipher->auth_tag_len_ == kNoAuthTagLength) {
28812901
return args.GetReturnValue().SetUndefined();
28822902
}
28832903

@@ -2902,7 +2922,9 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
29022922
unsigned int tag_len = Buffer::Length(args[0]);
29032923
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_);
29042924
if (mode == EVP_CIPH_GCM_MODE) {
2905-
if (tag_len > 16 || (tag_len < 12 && tag_len != 8 && tag_len != 4)) {
2925+
if (cipher->auth_tag_len_ != kNoAuthTagLength &&
2926+
cipher->auth_tag_len_ != tag_len ||
2927+
!IsValidGCMTagLength(tag_len)) {
29062928
char msg[50];
29072929
snprintf(msg, sizeof(msg),
29082930
"Invalid GCM authentication tag length: %u", tag_len);
@@ -2938,7 +2960,8 @@ bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
29382960
if (!CheckCCMMessageLength(plaintext_len))
29392961
return false;
29402962

2941-
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0) {
2963+
if (kind_ == kDecipher && !auth_tag_set_ && auth_tag_len_ > 0 &&
2964+
auth_tag_len_ != kNoAuthTagLength) {
29422965
if (!EVP_CIPHER_CTX_ctrl(ctx_,
29432966
EVP_CTRL_CCM_SET_TAG,
29442967
auth_tag_len_,
@@ -2991,7 +3014,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
29913014

29923015
// on first update:
29933016
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
2994-
!auth_tag_set_) {
3017+
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
29953018
EVP_CIPHER_CTX_ctrl(ctx_,
29963019
EVP_CTRL_GCM_SET_TAG,
29973020
auth_tag_len_,

src/node_crypto.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ class CipherBase : public BaseObject {
359359
kErrorMessageSize,
360360
kErrorState
361361
};
362+
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
362363

363364
void Init(const char* cipher_type,
364365
const char* key_buf,
@@ -398,7 +399,7 @@ class CipherBase : public BaseObject {
398399
ctx_(nullptr),
399400
kind_(kind),
400401
auth_tag_set_(false),
401-
auth_tag_len_(0),
402+
auth_tag_len_(kNoAuthTagLength),
402403
pending_auth_failed_(false) {
403404
MakeWeak<CipherBase>(this);
404405
}

test/parallel/test-crypto-authenticated.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,9 +726,46 @@ for (const test of TEST_CASES) {
726726
type: Error,
727727
message: `Invalid GCM authentication tag length: ${length}`
728728
});
729+
730+
common.expectsError(() => {
731+
crypto.createDecipheriv('aes-256-gcm',
732+
'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
733+
'qkuZpJWCewa6Szih',
734+
{
735+
authTagLength: length
736+
});
737+
}, {
738+
type: Error,
739+
message: `Invalid GCM authentication tag length: ${length}`
740+
});
729741
}
730742
}
731743

744+
// Test that users can manually restrict the GCM tag length to a single value.
745+
{
746+
const decipher = crypto.createDecipheriv('aes-256-gcm',
747+
'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
748+
'qkuZpJWCewa6Szih', {
749+
authTagLength: 8
750+
});
751+
752+
common.expectsError(() => {
753+
// This tag would normally be allowed.
754+
decipher.setAuthTag(Buffer.from('1'.repeat(12)));
755+
}, {
756+
type: Error,
757+
message: 'Invalid GCM authentication tag length: 12'
758+
});
759+
760+
// The Decipher object should be left intact.
761+
decipher.setAuthTag(Buffer.from('445352d3ff85cf94', 'hex'));
762+
const text = Buffer.concat([
763+
decipher.update('3a2a3647', 'hex'),
764+
decipher.final()
765+
]);
766+
assert.strictEqual(text.toString('utf8'), 'node');
767+
}
768+
732769
// Test that create(De|C)ipher(iv)? throws if the mode is CCM and an invalid
733770
// authentication tag length has been specified.
734771
{

0 commit comments

Comments
 (0)