From da098efcdb567628b661e01f3a02bb102607ac5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Tue, 21 Jun 2022 13:43:09 +0200 Subject: [PATCH] doc,test: clarify timingSafeEqual semantics PR-URL: https://github.com/nodejs/node/pull/43228 Reviewed-By: Ben Noordhuis Reviewed-By: Rich Trott --- doc/api/crypto.md | 13 +++++-- .../test-crypto-timing-safe-equal.js | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 7924ba2900034a..74b40c25429940 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -5340,8 +5340,11 @@ changes: * `b` {ArrayBuffer|Buffer|TypedArray|DataView} * Returns: {boolean} -This function is based on a constant-time algorithm. -Returns true if `a` is equal to `b`, without leaking timing information that +This function compares the underlying bytes that represent the given +`ArrayBuffer`, `TypedArray`, or `DataView` instances using a constant-time +algorithm. + +This function does not leak timing information that would allow an attacker to guess one of the values. This is suitable for comparing HMAC digests or secret values like authentication cookies or [capability urls](https://www.w3.org/TR/capability-urls/). @@ -5354,6 +5357,12 @@ If at least one of `a` and `b` is a `TypedArray` with more than one byte per entry, such as `Uint16Array`, the result will be computed using the platform byte order. +When both of the inputs are `Float32Array`s or +`Float64Array`s, this function might return unexpected results due to IEEE 754 +encoding of floating-point numbers. In particular, neither `x === y` nor +`Object.is(x, y)` implies that the byte representations of two floating-point +numbers `x` and `y` are equal. + Use of `crypto.timingSafeEqual` does not guarantee that the _surrounding_ code is timing-safe. Care should be taken to ensure that the surrounding code does not introduce timing vulnerabilities. diff --git a/test/sequential/test-crypto-timing-safe-equal.js b/test/sequential/test-crypto-timing-safe-equal.js index a8bd3abf4cf1ae..13190692b84749 100644 --- a/test/sequential/test-crypto-timing-safe-equal.js +++ b/test/sequential/test-crypto-timing-safe-equal.js @@ -32,6 +32,41 @@ assert.strictEqual( } } +{ + // When the inputs are floating-point numbers, timingSafeEqual neither has + // equality nor SameValue semantics. It just compares the underlying bytes, + // ignoring the TypedArray type completely. + + const cmp = (fn) => (a, b) => a.every((x, i) => fn(x, b[i])); + const eq = cmp((a, b) => a === b); + const is = cmp(Object.is); + + function test(a, b, { equal, sameValue, timingSafeEqual }) { + assert.strictEqual(eq(a, b), equal); + assert.strictEqual(is(a, b), sameValue); + assert.strictEqual(crypto.timingSafeEqual(a, b), timingSafeEqual); + } + + test(new Float32Array([NaN]), new Float32Array([NaN]), { + equal: false, + sameValue: true, + timingSafeEqual: true + }); + + test(new Float64Array([0]), new Float64Array([-0]), { + equal: true, + sameValue: false, + timingSafeEqual: false + }); + + const x = new BigInt64Array([0x7ff0000000000001n, 0xfff0000000000001n]); + test(new Float64Array(x.buffer), new Float64Array([NaN, NaN]), { + equal: false, + sameValue: true, + timingSafeEqual: false + }); +} + assert.throws( () => crypto.timingSafeEqual(Buffer.from([1, 2, 3]), Buffer.from([1, 2])), {