From 08609b5222437ee5c4f89d3901c23586a2637fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 8 Apr 2024 13:36:53 +0200 Subject: [PATCH] crypto: make timingSafeEqual faster for Uint8Array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a fast API that V8 can use if the user supplies Uint8Arrays (including Buffers) to timingSafeEqual. PR-URL: https://github.com/nodejs/node/pull/52341 Reviewed-By: Yagiz Nizipli Reviewed-By: Vinícius Lourenço Claro Cardoso Reviewed-By: Daniel Lemire Reviewed-By: Benjamin Gruenbaum Reviewed-By: Matteo Collina --- benchmark/crypto/timingSafeEqual.js | 22 ++++++++++++++++++++++ src/crypto/crypto_timing.cc | 26 ++++++++++++++++++++++++-- src/node_external_reference.h | 6 ++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 benchmark/crypto/timingSafeEqual.js diff --git a/benchmark/crypto/timingSafeEqual.js b/benchmark/crypto/timingSafeEqual.js new file mode 100644 index 00000000000..475807dba4e --- /dev/null +++ b/benchmark/crypto/timingSafeEqual.js @@ -0,0 +1,22 @@ +'use strict'; + +const common = require('../common.js'); +const assert = require('node:assert'); +const { randomBytes, timingSafeEqual } = require('node:crypto'); + +const bench = common.createBenchmark(main, { + n: [1e5], + bufferSize: [10, 100, 200, 2_100, 22_023], +}); + +function main({ n, bufferSize }) { + const bufs = [randomBytes(bufferSize), randomBytes(bufferSize)]; + bench.start(); + let count = 0; + for (let i = 0; i < n; i++) { + const ret = timingSafeEqual(bufs[i % 2], bufs[1]); + if (ret) count++; + } + bench.end(n); + assert.strictEqual(count, Math.floor(n / 2)); +} diff --git a/src/crypto/crypto_timing.cc b/src/crypto/crypto_timing.cc index 3d876fc4c30..103a620d637 100644 --- a/src/crypto/crypto_timing.cc +++ b/src/crypto/crypto_timing.cc @@ -9,6 +9,8 @@ namespace node { +using v8::FastApiCallbackOptions; +using v8::FastApiTypedArray; using v8::FunctionCallbackInfo; using v8::Local; using v8::Object; @@ -46,12 +48,32 @@ void TimingSafeEqual(const FunctionCallbackInfo& args) { CRYPTO_memcmp(buf1.data(), buf2.data(), buf1.size()) == 0); } +bool FastTimingSafeEqual(Local receiver, + const FastApiTypedArray& a, + const FastApiTypedArray& b, + // NOLINTNEXTLINE(runtime/references) + FastApiCallbackOptions& options) { + uint8_t* data_a; + uint8_t* data_b; + if (a.length() != b.length() || !a.getStorageIfAligned(&data_a) || + !b.getStorageIfAligned(&data_b)) { + options.fallback = true; + return false; + } + + return CRYPTO_memcmp(data_a, data_b, a.length()) == 0; +} + +static v8::CFunction fast_equal(v8::CFunction::Make(FastTimingSafeEqual)); + void Initialize(Environment* env, Local target) { - SetMethodNoSideEffect( - env->context(), target, "timingSafeEqual", TimingSafeEqual); + SetFastMethodNoSideEffect( + env->context(), target, "timingSafeEqual", TimingSafeEqual, &fast_equal); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(TimingSafeEqual); + registry->Register(FastTimingSafeEqual); + registry->Register(fast_equal.GetTypeInfo()); } } // namespace Timing diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 8b47831769d..eec8a0e3f07 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -27,6 +27,11 @@ using CFunctionCallbackWithStrings = bool (*)(v8::Local, const v8::FastOneByteString& input, const v8::FastOneByteString& base); +using CFunctionCallbackWithTwoUint8ArraysFallback = + bool (*)(v8::Local, + const v8::FastApiTypedArray&, + const v8::FastApiTypedArray&, + v8::FastApiCallbackOptions&); using CFunctionWithUint32 = uint32_t (*)(v8::Local, const uint32_t input); using CFunctionWithDoubleReturnDouble = double (*)(v8::Local, @@ -51,6 +56,7 @@ class ExternalReferenceRegistry { V(CFunctionCallbackWithBool) \ V(CFunctionCallbackWithString) \ V(CFunctionCallbackWithStrings) \ + V(CFunctionCallbackWithTwoUint8ArraysFallback) \ V(CFunctionWithUint32) \ V(CFunctionWithDoubleReturnDouble) \ V(CFunctionWithInt64Fallback) \