Permalink
Browse files

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>
  • Loading branch information...
tniessen committed Apr 13, 2018
1 parent 854f840 commit 358d8ffad650b6fb966082e7bd1460b0d6a4eacc
Showing with 74 additions and 7 deletions.
  1. +7 −1 doc/api/crypto.md
  2. +28 −5 src/node_crypto.cc
  3. +2 −1 src/node_crypto.h
  4. +37 −0 test/parallel/test-crypto-authenticated.js
View
@@ -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
View
@@ -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_,
View
@@ -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.
{

0 comments on commit 358d8ff

Please sign in to comment.