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: https://github.com/nodejs/node/pull/20039 Fixes: https://github.com/nodejs/node/issues/17523 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yihong Wang <yh.wang@ibm.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
854f840243
commit
358d8ffad6
@ -1457,6 +1457,10 @@ to create the `Decipher` object.
|
|||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.1.94
|
added: v0.1.94
|
||||||
changes:
|
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
|
- version: v9.9.0
|
||||||
pr-url: https://github.com/nodejs/node/pull/18644
|
pr-url: https://github.com/nodejs/node/pull/18644
|
||||||
description: The `iv` parameter may now be `null` for ciphers which do not
|
description: The `iv` parameter may now be `null` for ciphers which do not
|
||||||
@ -1474,7 +1478,9 @@ and initialization vector (`iv`).
|
|||||||
The `options` argument controls stream behavior and is optional except when a
|
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
|
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
|
`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
|
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
|
||||||
recent OpenSSL releases, `openssl list-cipher-algorithms` will display the
|
recent OpenSSL releases, `openssl list-cipher-algorithms` will display the
|
||||||
|
@ -2797,6 +2797,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,
|
bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
|
||||||
int auth_tag_len) {
|
int auth_tag_len) {
|
||||||
CHECK(IsAuthenticatedMode());
|
CHECK(IsAuthenticatedMode());
|
||||||
@ -2809,7 +2813,8 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
|
|||||||
return false;
|
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) {
|
if (auth_tag_len < 0) {
|
||||||
char msg[128];
|
char msg[128];
|
||||||
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
|
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
|
||||||
@ -2842,6 +2847,21 @@ bool CipherBase::InitAuthenticated(const char *cipher_type, int iv_len,
|
|||||||
} else {
|
} else {
|
||||||
max_message_size_ = INT_MAX;
|
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;
|
return true;
|
||||||
@ -2877,7 +2897,7 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|||||||
// Only callable after Final and if encrypting.
|
// Only callable after Final and if encrypting.
|
||||||
if (cipher->ctx_ != nullptr ||
|
if (cipher->ctx_ != nullptr ||
|
||||||
cipher->kind_ != kCipher ||
|
cipher->kind_ != kCipher ||
|
||||||
cipher->auth_tag_len_ == 0) {
|
cipher->auth_tag_len_ == kNoAuthTagLength) {
|
||||||
return args.GetReturnValue().SetUndefined();
|
return args.GetReturnValue().SetUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2902,7 +2922,9 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|||||||
unsigned int tag_len = Buffer::Length(args[0]);
|
unsigned int tag_len = Buffer::Length(args[0]);
|
||||||
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_);
|
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_);
|
||||||
if (mode == EVP_CIPH_GCM_MODE) {
|
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 ||
|
||||||
|
!IsValidGCMTagLength(tag_len)) {
|
||||||
char msg[50];
|
char msg[50];
|
||||||
snprintf(msg, sizeof(msg),
|
snprintf(msg, sizeof(msg),
|
||||||
"Invalid GCM authentication tag length: %u", tag_len);
|
"Invalid GCM authentication tag length: %u", tag_len);
|
||||||
@ -2938,7 +2960,8 @@ bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
|
|||||||
if (!CheckCCMMessageLength(plaintext_len))
|
if (!CheckCCMMessageLength(plaintext_len))
|
||||||
return false;
|
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_,
|
if (!EVP_CIPHER_CTX_ctrl(ctx_,
|
||||||
EVP_CTRL_CCM_SET_TAG,
|
EVP_CTRL_CCM_SET_TAG,
|
||||||
auth_tag_len_,
|
auth_tag_len_,
|
||||||
@ -2991,7 +3014,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
|
|||||||
|
|
||||||
// on first update:
|
// on first update:
|
||||||
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
|
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
|
||||||
!auth_tag_set_) {
|
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
|
||||||
EVP_CIPHER_CTX_ctrl(ctx_,
|
EVP_CIPHER_CTX_ctrl(ctx_,
|
||||||
EVP_CTRL_GCM_SET_TAG,
|
EVP_CTRL_GCM_SET_TAG,
|
||||||
auth_tag_len_,
|
auth_tag_len_,
|
||||||
|
@ -359,6 +359,7 @@ class CipherBase : public BaseObject {
|
|||||||
kErrorMessageSize,
|
kErrorMessageSize,
|
||||||
kErrorState
|
kErrorState
|
||||||
};
|
};
|
||||||
|
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
|
||||||
|
|
||||||
void Init(const char* cipher_type,
|
void Init(const char* cipher_type,
|
||||||
const char* key_buf,
|
const char* key_buf,
|
||||||
@ -398,7 +399,7 @@ class CipherBase : public BaseObject {
|
|||||||
ctx_(nullptr),
|
ctx_(nullptr),
|
||||||
kind_(kind),
|
kind_(kind),
|
||||||
auth_tag_set_(false),
|
auth_tag_set_(false),
|
||||||
auth_tag_len_(0),
|
auth_tag_len_(kNoAuthTagLength),
|
||||||
pending_auth_failed_(false) {
|
pending_auth_failed_(false) {
|
||||||
MakeWeak<CipherBase>(this);
|
MakeWeak<CipherBase>(this);
|
||||||
}
|
}
|
||||||
|
@ -726,9 +726,46 @@ for (const test of TEST_CASES) {
|
|||||||
type: Error,
|
type: Error,
|
||||||
message: `Invalid GCM authentication tag length: ${length}`
|
message: `Invalid GCM authentication tag length: ${length}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
// Test that create(De|C)ipher(iv)? throws if the mode is CCM and an invalid
|
||||||
// authentication tag length has been specified.
|
// authentication tag length has been specified.
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user