Skip to content

Commit 39925de

Browse files
panvamarco-ippolito
authored andcommitted
crypto: allow non-multiple of 8 in SubtleCrypto.deriveBits
PR-URL: #55296 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent af1dc63 commit 39925de

File tree

4 files changed

+28
-17
lines changed

4 files changed

+28
-17
lines changed

doc/api/webcrypto.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -596,9 +596,6 @@ Using the method and parameters specified in `algorithm` and the keying
596596
material provided by `baseKey`, `subtle.deriveBits()` attempts to generate
597597
`length` bits.
598598

599-
The Node.js implementation requires that `length`, when a number, is a multiple
600-
of `8`.
601-
602599
When `length` is not provided or `null` the maximum number of bits for a given
603600
algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'`
604601
algorithms, for other algorithms `length` is required to be a number.

lib/internal/crypto/diffiehellman.js

Lines changed: 18 additions & 8 deletions
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) {
@@ -342,18 +345,25 @@ async function ecdhDeriveBits(algorithm, baseKey, length) {
342345

343346
// If the length is not a multiple of 8 the nearest ceiled
344347
// multiple of 8 is sliced.
345-
length = MathCeil(length / 8);
346-
const { byteLength } = bits;
348+
const sliceLength = MathCeil(length / 8);
347349

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

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

359369
module.exports = {

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

Lines changed: 5 additions & 3 deletions
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

Lines changed: 5 additions & 3 deletions
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

0 commit comments

Comments
 (0)