Skip to content

Commit 55938d2

Browse files
committed
crypto: allow non-multiple of 8 in SubtleCrypto.deriveBits
1 parent 36ca010 commit 55938d2

File tree

4 files changed

+46
-17
lines changed

4 files changed

+46
-17
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

+36-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');
@@ -342,18 +343,45 @@ async function ecdhDeriveBits(algorithm, baseKey, length) {
342343

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

348+
const { byteLength } = bits;
348349
// 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)
350+
if (byteLength < sliceLength)
352351
throw lazyDOMException('derived bit length is too small', 'OperationError');
353352

354-
return length === byteLength ?
355-
bits :
356-
ArrayBufferPrototypeSlice(bits, 0, length);
353+
const slice = ArrayBufferPrototypeSlice(bits, 0, sliceLength);
354+
355+
let mask;
356+
switch (length % 8) {
357+
case 0:
358+
return slice;
359+
case 1:
360+
mask = 0b10000000;
361+
break;
362+
case 2:
363+
mask = 0b11000000;
364+
break;
365+
case 3:
366+
mask = 0b11100000;
367+
break;
368+
case 4:
369+
mask = 0b11110000;
370+
break;
371+
case 5:
372+
mask = 0b11111000;
373+
break;
374+
case 6:
375+
mask = 0b11111100;
376+
break;
377+
case 7:
378+
mask = 0b11111110;
379+
break;
380+
}
381+
382+
const masked = new Uint8Array(slice);
383+
masked[sliceLength - 1] = masked[sliceLength - 1] & mask;
384+
return masked.buffer;
357385
}
358386

359387
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

0 commit comments

Comments
 (0)