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
|
||||
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
|
||||
@ -1474,7 +1478,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` 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,
|
||||
int auth_tag_len) {
|
||||
CHECK(IsAuthenticatedMode());
|
||||
@ -2809,7 +2813,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);
|
||||
@ -2842,6 +2847,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;
|
||||
@ -2877,7 +2897,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();
|
||||
}
|
||||
|
||||
@ -2902,7 +2922,9 @@ 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 ||
|
||||
!IsValidGCMTagLength(tag_len)) {
|
||||
char msg[50];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"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))
|
||||
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_,
|
||||
@ -2991,7 +3014,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_,
|
||||
|
@ -359,6 +359,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,
|
||||
@ -398,7 +399,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<CipherBase>(this);
|
||||
}
|
||||
|
@ -726,9 +726,46 @@ for (const test of TEST_CASES) {
|
||||
type: Error,
|
||||
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
|
||||
// authentication tag length has been specified.
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user