diff options
author | Tobias Nießen <tniessen@tnie.de> | 2018-04-13 18:02:46 +0200 |
---|---|---|
committer | Tobias Nießen <tniessen@tnie.de> | 2018-04-23 01:55:09 +0200 |
commit | 358d8ffad650b6fb966082e7bd1460b0d6a4eacc (patch) | |
tree | ff6262822e193167ec4758a92cea0bf03216d33f | |
parent | 854f840243b0b531f9569f9304a6810b01bd9778 (diff) | |
download | android-node-v8-358d8ffad650b6fb966082e7bd1460b0d6a4eacc.tar.gz android-node-v8-358d8ffad650b6fb966082e7bd1460b0d6a4eacc.tar.bz2 android-node-v8-358d8ffad650b6fb966082e7bd1460b0d6a4eacc.zip |
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>
-rw-r--r-- | doc/api/crypto.md | 8 | ||||
-rw-r--r-- | src/node_crypto.cc | 33 | ||||
-rw-r--r-- | src/node_crypto.h | 3 | ||||
-rw-r--r-- | test/parallel/test-crypto-authenticated.js | 37 |
4 files changed, 74 insertions, 7 deletions
diff --git a/doc/api/crypto.md b/doc/api/crypto.md index b4ff52d89d..47181f6a2c 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -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 diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 2bdda4566d..255dc6e2d4 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -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_, diff --git a/src/node_crypto.h b/src/node_crypto.h index 2f7c904ee9..3c166f5dcc 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -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); } diff --git a/test/parallel/test-crypto-authenticated.js b/test/parallel/test-crypto-authenticated.js index df5ba03923..6c41538752 100644 --- a/test/parallel/test-crypto-authenticated.js +++ b/test/parallel/test-crypto-authenticated.js @@ -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. { |