Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bdd2856

Browse filesBrowse 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 <jasnell@gmail.com> Reviewed-By: Yihong Wang <yh.wang@ibm.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
1 parent b6ea5df commit bdd2856
Copy full SHA for bdd2856

File tree

Expand file treeCollapse file tree

4 files changed

+82
-7
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+82
-7
lines changed
Open diff view settings
Collapse file

‎doc/api/crypto.md‎

Copy file name to clipboardExpand all lines: doc/api/crypto.md
+7-1Lines changed: 7 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
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`
Collapse file

‎src/node_crypto.cc‎

Copy file name to clipboardExpand all lines: src/node_crypto.cc
+33-5Lines changed: 33 additions & 5 deletions
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_,
Collapse file

‎src/node_crypto.h‎

Copy file name to clipboardExpand all lines: src/node_crypto.h
+2-1Lines changed: 2 additions & 1 deletion
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
}
Collapse file

‎test/parallel/test-crypto-authenticated.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-crypto-authenticated.js
+40Lines changed: 40 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.