From 8f61b658de1e440839a076d3e5337193af960239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Wed, 10 Apr 2024 10:16:33 +0200 Subject: [PATCH] crypto: deprecate implicitly shortened GCM tags This introduces a doc-only deprecation of using GCM authentication tags that are shorter than the cipher's block size, unless the user specified the authTagLength option. Refs: https://github.com/nodejs/node/issues/52327 PR-URL: https://github.com/nodejs/node/pull/52345 Reviewed-By: Filip Skokan Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell --- doc/api/crypto.md | 5 ++ doc/api/deprecations.md | 19 ++++++++ src/crypto/crypto_cipher.cc | 13 +++++ test/common/index.js | 4 +- .../test-crypto-gcm-explicit-short-tag.js | 47 +++++++++++++++++++ .../test-crypto-gcm-implicit-short-tag.js | 21 +++++++++ 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-crypto-gcm-explicit-short-tag.js create mode 100644 test/parallel/test-crypto-gcm-implicit-short-tag.js diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 8b0ca04b486600..44a4e249759b3a 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -891,6 +891,11 @@ When passing a string as the `buffer`, please consider + +Type: Documentation-only (supports [`--pending-deprecation`][]) + +Applications that intend to use authentication tags that are shorter than the +default authentication tag length should set the `authTagLength` option of the +[`crypto.createDecipheriv()`][] function to the appropriate length. + +For ciphers in GCM mode, the [`decipher.setAuthTag()`][] function accepts +authentication tags of any valid length (see [DEP0090](#DEP0090)). This behavior +is deprecated to better align with recommendations per [NIST SP 800-38D][]. + [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3 [RFC 8247 Section 2.4]: https://www.rfc-editor.org/rfc/rfc8247#section-2.4 diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 4b5cdca9719da5..94e2957cadec16 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -697,6 +697,19 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { env, "Invalid authentication tag length: %u", tag_len); } + if (mode == EVP_CIPH_GCM_MODE && cipher->auth_tag_len_ == kNoAuthTagLength && + tag_len != 16 && env->options()->pending_deprecation && + env->EmitProcessEnvWarning()) { + if (ProcessEmitDeprecationWarning( + env, + "Using AES-GCM authentication tags of less than 128 bits without " + "specifying the authTagLength option when initializing decryption " + "is deprecated.", + "DEP0182") + .IsNothing()) + return; + } + cipher->auth_tag_len_ = tag_len; cipher->auth_tag_state_ = kAuthTagKnown; CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_)); diff --git a/test/common/index.js b/test/common/index.js index 74e583603fda3b..a712e6d14c4c16 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -659,12 +659,12 @@ function _expectWarning(name, expected, code) { expected = [[expected, code]]; } else if (!Array.isArray(expected)) { expected = Object.entries(expected).map(([a, b]) => [b, a]); - } else if (!(Array.isArray(expected[0]))) { + } else if (expected.length !== 0 && !Array.isArray(expected[0])) { expected = [[expected[0], expected[1]]]; } // Deprecation codes are mandatory, everything else is not. if (name === 'DeprecationWarning') { - expected.forEach(([_, code]) => assert(code, expected)); + expected.forEach(([_, code]) => assert(code, `Missing deprecation code: ${expected}`)); } return mustCall((warning) => { const expectedProperties = expected.shift(); diff --git a/test/parallel/test-crypto-gcm-explicit-short-tag.js b/test/parallel/test-crypto-gcm-explicit-short-tag.js new file mode 100644 index 00000000000000..ec0d70444cc57d --- /dev/null +++ b/test/parallel/test-crypto-gcm-explicit-short-tag.js @@ -0,0 +1,47 @@ +// Flags: --pending-deprecation +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { createDecipheriv, randomBytes } = require('crypto'); + +common.expectWarning({ + DeprecationWarning: [] +}); + +const key = randomBytes(32); +const iv = randomBytes(16); + +{ + // Full 128-bit tag. + + const tag = randomBytes(16); + createDecipheriv('aes-256-gcm', key, iv).setAuthTag(tag); +} + +{ + // Shortened tag with explicit length option. + + const tag = randomBytes(12); + createDecipheriv('aes-256-gcm', key, iv, { + authTagLength: tag.byteLength + }).setAuthTag(tag); +} + +{ + // Shortened tag with explicit but incorrect length option. + + const tag = randomBytes(12); + assert.throws(() => { + createDecipheriv('aes-256-gcm', key, iv, { + authTagLength: 14 + }).setAuthTag(tag); + }, { + name: 'TypeError', + message: 'Invalid authentication tag length: 12', + code: 'ERR_CRYPTO_INVALID_AUTH_TAG' + }); +} diff --git a/test/parallel/test-crypto-gcm-implicit-short-tag.js b/test/parallel/test-crypto-gcm-implicit-short-tag.js new file mode 100644 index 00000000000000..0776506bb63523 --- /dev/null +++ b/test/parallel/test-crypto-gcm-implicit-short-tag.js @@ -0,0 +1,21 @@ +// Flags: --pending-deprecation +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { createDecipheriv, randomBytes } = require('crypto'); + +common.expectWarning({ + DeprecationWarning: [ + ['Using AES-GCM authentication tags of less than 128 bits without ' + + 'specifying the authTagLength option when initializing decryption is ' + + 'deprecated.', + 'DEP0182'], + ] +}); + +const key = randomBytes(32); +const iv = randomBytes(16); +const tag = randomBytes(12); +createDecipheriv('aes-256-gcm', key, iv).setAuthTag(tag);