Repository: nodejs/node
Pull request: src: simplify handling of kNoAuthTagLength

Since https://github.com/nodejs/node/pull/61084, the `kNoAuthTagLength` constant is only used

- during the entire lifetime of non-authenticating cipher/decipher objects and
- during the initialization of authenticating cipher/decipher objects.

In particular, it is never used during the lifetime of an authenticating cipher/decipher object once that object has been initialized. (Before said change, the constant was also used for ciphers in GCM mode to indicate that the user had not specified the length of the authentication tag in advance, but the authentication tag length must now always be specified or defaults to the block size in the case of GCM mode.)

This commit simplifies the handling of `kNoAuthTagLength` based on the knowledge that it is not a valid value for AEAD ciphers beyond initialization.

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: src/crypto/crypto_cipher.cc
patch: @@ -427,61 +427,44 @@ bool CipherBase::InitAuthenticated(const char* cipher_type,
     return false;
   }
 
-  if (ctx_.isGcmMode()) {
-    if (auth_tag_len != kNoAuthTagLength) {
-      if (!Cipher::IsValidGCMTagLength(auth_tag_len)) {
-        THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
-          env(),
-          "Invalid authentication tag length: %u",
-          auth_tag_len);
-        return false;
-      }
-
-      // Remember the given authentication tag length for later.
-      auth_tag_len_ = auth_tag_len;
-    }
-  } else {
-    if (auth_tag_len == kNoAuthTagLength) {
-      // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag
-      // length defaults to 16 bytes when encrypting. Unlike GCM, the
-      // authentication tag length also defaults to 16 bytes when decrypting,
-      // whereas GCM would accept any valid authentication tag length.
-      if (ctx_.isChaCha20Poly1305()) {
-        auth_tag_len = EVP_CHACHAPOLY_TLS_TAG_LEN;
-      } else {
-        THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
-          env(), "authTagLength required for %s", cipher_type);
-        return false;
-      }
-    }
-
+  if (ctx_.isCcmMode()) {
     // TODO(tniessen) Support CCM decryption in FIPS mode
-
-    if (ctx_.isCcmMode() && kind_ == kDecipher && ncrypto::isFipsEnabled()) {
-      THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
-          "CCM encryption not supported in FIPS mode");
+    if (kind_ == kDecipher && ncrypto::isFipsEnabled()) {
+      THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
+          env(), "CCM encryption not supported in FIPS mode");
       return false;
     }
 
-    // Tell OpenSSL about the desired length.
-    if (!ctx_.setAeadTagLength(auth_tag_len)) {
+    // Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
+    CHECK(iv_len >= 7 && iv_len <= 13);
+    max_message_size_ = INT_MAX;
+    if (iv_len == 12) max_message_size_ = 16777215;
+    if (iv_len == 13) max_message_size_ = 65535;
+  }
+
+  if (auth_tag_len == kNoAuthTagLength) {
+    // Both GCM and ChaCha20-Poly1305 have a default tag length of 16 bytes.
+    // Other modes (CCM, OCB) require an explicit tag length.
+    if (ctx_.isGcmMode()) {
+      auth_tag_len = EVP_GCM_TLS_TAG_LEN;
+    } else if (ctx_.isChaCha20Poly1305()) {
+      auth_tag_len = EVP_CHACHAPOLY_TLS_TAG_LEN;
+    } else {
       THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
-          env(), "Invalid authentication tag length: %u", auth_tag_len);
+          env(), "authTagLength required for %s", cipher_type);
       return false;
     }
-
-    // Remember the given authentication tag length for later.
-    auth_tag_len_ = auth_tag_len;
-
-    if (ctx_.isCcmMode()) {
-      // Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
-      CHECK(iv_len >= 7 && iv_len <= 13);
-      max_message_size_ = INT_MAX;
-      if (iv_len == 12) max_message_size_ = 16777215;
-      if (iv_len == 13) max_message_size_ = 65535;
-    }
+  } else if ((ctx_.isGcmMode() && !Cipher::IsValidGCMTagLength(auth_tag_len)) ||
+             (!ctx_.isGcmMode() && !ctx_.setAeadTagLength(auth_tag_len))) {
+    // GCM authentication tag lengths are restricted according to NIST 800-38d,
+    // page 9. For other modes, we rely on OpenSSL to validate the length.
+    THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
+        env(), "Invalid authentication tag length: %u", auth_tag_len);
+    return false;
   }
 
+  // Remember the given authentication tag length for later.
+  auth_tag_len_ = auth_tag_len;
   return true;
 }
 
@@ -510,11 +493,11 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
 
   // Only callable after Final and if encrypting.
   if (cipher->ctx_ || cipher->kind_ != kCipher ||
-      cipher->auth_tag_len_ == kNoAuthTagLength ||
       cipher->auth_tag_state_ != kAuthTagComputed) {
     return;
   }
 
+  CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
   Local<Value> ret;
   if (Buffer::Copy(env, cipher->auth_tag_, cipher->auth_tag_len_)
           .ToLocal(&ret)) {
@@ -540,30 +523,17 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
   }
   unsigned int tag_len = auth_tag.size();
 
-  bool is_valid;
-  if (cipher->ctx_.isGcmMode()) {
-    // Restrict GCM tag lengths according to NIST 800-38d, page 9.
-    is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength ||
-                cipher->auth_tag_len_ == tag_len) &&
-               Cipher::IsValidGCMTagLength(tag_len);
-  } else {
-    // At this point, the tag length is already known and must match the
-    // length of the given authentication tag.
-    CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
-    is_valid = cipher->auth_tag_len_ == tag_len;
-  }
-
-  // TODO(tniessen): refactor this check.
-  if (!is_valid ||
-      (cipher->ctx_.isGcmMode() && cipher->auth_tag_len_ == kNoAuthTagLength &&
-       tag_len != EVP_GCM_TLS_TAG_LEN)) {
+  // Older versions of Node.js did allow setting the auth tag length implicitly
+  // for GCM mode (see DEP0182). We now require knowledge of the expected tag
+  // length up front, so it must have been set during initialization.
+  // The configured tag length must match the length of the given auth tag.
+  CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
+  if (cipher->auth_tag_len_ != tag_len) {
     return THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
       env, "Invalid authentication tag length: %u", tag_len);
   }
 
-  cipher->auth_tag_len_ = tag_len;
   CHECK_LE(cipher->auth_tag_len_, ncrypto::Cipher::MAX_AUTH_TAG_LENGTH);
-
   if (!cipher->ctx_.setAeadTag({auth_tag.data(), cipher->auth_tag_len_})) {
     return args.GetReturnValue().Set(false);
   }
@@ -772,13 +742,7 @@ bool CipherBase::Final(std::unique_ptr<BackingStore>* out) {
     }
 
     if (ok && kind_ == kCipher && IsAuthenticatedMode()) {
-      // In GCM mode, the authentication tag length can be specified in advance,
-      // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
-      // always be given by the user.
-      if (auth_tag_len_ == kNoAuthTagLength) {
-        CHECK(ctx_.isGcmMode());
-        auth_tag_len_ = EVP_GCM_TLS_TAG_LEN;
-      }
+      CHECK_NE(auth_tag_len_, kNoAuthTagLength);
       ok = ctx_.getAeadTag(auth_tag_len_,
                            reinterpret_cast<unsigned char*>(auth_tag_));
       if (ok) {