Commit 686b3464db7 for nodejs
commit 686b3464db7f4cffb2bd9173145225f399b27bb2
Author: Filip Skokan <panva.ip@gmail.com>
Date: Wed Sep 23 14:38:13 2026 +0200
crypto: verify empty KMAC outputs
Compare empty KMAC outputs with empty signatures as equal, as required
by the verification algorithm. Skip CRYPTO_memcmp for zero-length
buffers while retaining the constant-time comparison for other MACs.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/src/crypto/crypto_kmac.cc b/src/crypto/crypto_kmac.cc
index f7a994b9e12..40b39990737 100644
--- a/src/crypto/crypto_kmac.cc
+++ b/src/crypto/crypto_kmac.cc
@@ -191,9 +191,10 @@ MaybeLocal<Value> KmacTraits::EncodeOutput(Environment* env,
case SignConfiguration::Mode::Verify:
return Boolean::New(
env->isolate(),
- out->size() > 0 && out->size() == params.signature.size() &&
- CRYPTO_memcmp(
- out->data(), params.signature.data(), out->size()) == 0);
+ out->size() == params.signature.size() &&
+ (out->size() == 0 ||
+ CRYPTO_memcmp(
+ out->data(), params.signature.data(), out->size()) == 0));
}
UNREACHABLE();
}
diff --git a/test/parallel/test-webcrypto-kmac-empty-output.js b/test/parallel/test-webcrypto-kmac-empty-output.js
new file mode 100644
index 00000000000..fe88f7ab769
--- /dev/null
+++ b/test/parallel/test-webcrypto-kmac-empty-output.js
@@ -0,0 +1,42 @@
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const { hasFIPS, hasOpenSSL } = require('../common/crypto');
+
+if (!hasOpenSSL(3))
+ common.skip('requires OpenSSL >= 3');
+if (hasFIPS())
+ common.skip('empty KMAC output is not supported in FIPS mode');
+
+const assert = require('assert');
+const { subtle } = globalThis.crypto;
+
+(async () => {
+ const data = new Uint8Array([1, 2, 3]);
+
+ for (const name of ['KMAC128', 'KMAC256']) {
+ const key = await subtle.importKey(
+ 'raw-secret', new Uint8Array(32), name, false, ['sign', 'verify']);
+ const otherKey = await subtle.importKey(
+ 'raw-secret', new Uint8Array(32).fill(1), name, false, ['verify']);
+ const algorithm = { name, outputLength: 0 };
+ const signature = await subtle.sign(algorithm, key, data);
+
+ assert.strictEqual(signature.byteLength, 0);
+ assert.strictEqual(await subtle.verify(algorithm, key, signature, data), true);
+ assert.strictEqual(await subtle.verify(algorithm, key, new Uint8Array([0]), data), false);
+ assert.strictEqual(await subtle.verify(
+ { name, outputLength: 256 }, key, signature, data), false);
+
+ // Empty MACs compare equal even when the inputs differ.
+ assert.strictEqual(await subtle.verify(
+ algorithm, key, signature, new Uint8Array([4, 5, 6])), true);
+ assert.strictEqual(await subtle.verify(algorithm, otherKey, signature, data), true);
+ assert.strictEqual(await subtle.verify(
+ { ...algorithm, customization: new Uint8Array([1]) }, key, signature, data), true);
+ }
+})().then(common.mustCall());