Skip to content

Commit 93d36bf

Browse files
panvaaduh95
authored andcommitted
crypto: allow non-multiple of 8 in SubtleCrypto.deriveBits
PR-URL: #55296 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cf3f7ac commit 93d36bf

File tree

5 files changed

+28
-27
lines changed

5 files changed

+28
-27
lines changed

doc/api/webcrypto.md

-3
Original file line numberDiff line numberDiff line change
@@ -600,9 +600,6 @@ Using the method and parameters specified in `algorithm` and the keying
600600
material provided by `baseKey`, `subtle.deriveBits()` attempts to generate
601601
`length` bits.
602602

603-
The Node.js implementation requires that `length`, when a number, is a multiple
604-
of `8`.
605-
606603
When `length` is not provided or `null` the maximum number of bits for a given
607604
algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'`
608605
algorithms, for other algorithms `length` is required to be a number.

lib/internal/crypto/diffiehellman.js

+18-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
MathCeil,
66
ObjectDefineProperty,
77
SafeSet,
8+
Uint8Array,
89
} = primordials;
910

1011
const { Buffer } = require('buffer');
@@ -295,6 +296,8 @@ function diffieHellman(options) {
295296
return statelessDH(privateKey[kHandle], publicKey[kHandle]);
296297
}
297298

299+
let masks;
300+
298301
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
299302
// deriveKeys and deriveBits functions.
300303
async function ecdhDeriveBits(algorithm, baseKey, length) {
@@ -341,18 +344,25 @@ async function ecdhDeriveBits(algorithm, baseKey, length) {
341344

342345
// If the length is not a multiple of 8 the nearest ceiled
343346
// multiple of 8 is sliced.
344-
length = MathCeil(length / 8);
345-
const { byteLength } = bits;
347+
const sliceLength = MathCeil(length / 8);
346348

349+
const { byteLength } = bits;
347350
// If the length is larger than the derived secret, throw.
348-
// Otherwise, we either return the secret or a truncated
349-
// slice.
350-
if (byteLength < length)
351+
if (byteLength < sliceLength)
351352
throw lazyDOMException('derived bit length is too small', 'OperationError');
352353

353-
return length === byteLength ?
354-
bits :
355-
ArrayBufferPrototypeSlice(bits, 0, length);
354+
const slice = ArrayBufferPrototypeSlice(bits, 0, sliceLength);
355+
356+
const mod = length % 8;
357+
if (mod === 0)
358+
return slice;
359+
360+
// eslint-disable-next-line no-sparse-arrays
361+
masks ||= [, 0b10000000, 0b11000000, 0b11100000, 0b11110000, 0b11111000, 0b11111100, 0b11111110];
362+
363+
const masked = new Uint8Array(slice);
364+
masked[sliceLength - 1] = masked[sliceLength - 1] & masks[mod];
365+
return masked.buffer;
356366
}
357367

358368
module.exports = {

test/parallel/test-webcrypto-derivebits-cfrg.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,11 @@ async function prepareKeys() {
140140
public: publicKey
141141
}, privateKey, 8 * size - 11);
142142

143-
assert.strictEqual(
144-
Buffer.from(bits).toString('hex'),
145-
result.slice(0, -2));
143+
const expected = Buffer.from(result.slice(0, -2), 'hex');
144+
expected[size - 2] = expected[size - 2] & 0b11111000;
145+
assert.deepStrictEqual(
146+
Buffer.from(bits),
147+
expected);
146148
}
147149
}));
148150

test/parallel/test-webcrypto-derivebits-ecdh.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,11 @@ async function prepareKeys() {
161161
public: publicKey
162162
}, privateKey, 8 * size - 11);
163163

164-
assert.strictEqual(
165-
Buffer.from(bits).toString('hex'),
166-
result.slice(0, -2));
164+
const expected = Buffer.from(result.slice(0, -2), 'hex');
165+
expected[size - 2] = expected[size - 2] & 0b11111000;
166+
assert.deepStrictEqual(
167+
Buffer.from(bits),
168+
expected);
167169
}
168170
}));
169171

test/wpt/status/WebCryptoAPI.cjs

-10
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,6 @@ module.exports = {
88
'algorithm-discards-context.https.window.js': {
99
'skip': 'Not relevant in Node.js context',
1010
},
11-
'derive_bits_keys/derived_bits_length.https.any.js': {
12-
'fail': {
13-
// See https://github.com/nodejs/node/pull/55296
14-
// The fix is pending a decision whether truncation in ECDH/X* will be removed from the spec entirely
15-
'expected': [
16-
"ECDH derivation with 230 as 'length' parameter",
17-
"X25519 derivation with 230 as 'length' parameter",
18-
],
19-
},
20-
},
2111
'historical.any.js': {
2212
'skip': 'Not relevant in Node.js context',
2313
},

0 commit comments

Comments
 (0)