Commit b77de58ef56 for nodejs
commit b77de58ef565e7614d164aa75569f886e2143974
Author: Filip Skokan <panva.ip@gmail.com>
Date: Tue Sep 22 22:32:12 2026 +0200
crypto: split hybrid keys without species
Create byte views directly when splitting hybrid KEM keys and seeds,
so typed-array species cannot alter the component key material.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/lib/internal/crypto/kem_hybrids.js b/lib/internal/crypto/kem_hybrids.js
index d95a651b61b..aa8fe27efed 100644
--- a/lib/internal/crypto/kem_hybrids.js
+++ b/lib/internal/crypto/kem_hybrids.js
@@ -8,9 +8,9 @@ const {
TypedArrayOf,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
+ TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeGetLength,
TypedArrayPrototypeSet,
- TypedArrayPrototypeSubarray,
Uint8Array,
} = primordials;
@@ -111,6 +111,12 @@ const kEncapsulationUsages = ['encapsulateKey', 'encapsulateBits'];
const kDecapsulationUsages = ['decapsulateKey', 'decapsulateBits'];
const kUsages = createKeyUsages(kEncapsulationUsages, kDecapsulationUsages);
+function getByteView(data, start, end = TypedArrayPrototypeGetByteLength(data)) {
+ return new Uint8Array(TypedArrayPrototypeGetBuffer(data),
+ TypedArrayPrototypeGetByteOffset(data) + start,
+ end - start);
+}
+
/**
* Adds lengths that are the concatenation of the PQ KEM component and the
* traditional group component.
@@ -289,8 +295,8 @@ function validateLength(data, length) {
function splitAt(data, offset) {
return {
__proto__: null,
- head: TypedArrayPrototypeSubarray(data, 0, offset),
- tail: TypedArrayPrototypeSubarray(data, offset),
+ head: getByteView(data, 0, offset),
+ tail: getByteView(data, offset),
};
}
@@ -353,7 +359,7 @@ function randomScalar(seed, config) {
offset + groupScalarLength <= TypedArrayPrototypeGetLength(seed);
offset += groupScalarLength) {
const scalar = copyBytes(
- TypedArrayPrototypeSubarray(seed, offset, offset + groupScalarLength));
+ getByteView(seed, offset, offset + groupScalarLength));
const value = os2ip(scalar);
if (value !== 0n && value < groupOrder)
return scalar;
@@ -761,7 +767,7 @@ function combineSharedSecret(
config) {
// C2PRICombiner(ss_PQ, ss_T, ct_T, ek_T, Label).
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hybrid-kems-12.html#section-5.1.3
- const encapsulationKeyT = TypedArrayPrototypeSubarray(
+ const encapsulationKeyT = getByteView(
encapsulationKey,
config.kemPqEncapsulationKeyLength);
const inputLength =
diff --git a/test/parallel/test-webcrypto-hybrid-kem-species.js b/test/parallel/test-webcrypto-hybrid-kem-species.js
new file mode 100644
index 00000000000..0521e0b7fc4
--- /dev/null
+++ b/test/parallel/test-webcrypto-hybrid-kem-species.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = globalThis.crypto;
+
+(async () => {
+ for (const name of ['MLKEM768-P256', 'MLKEM768-X25519', 'MLKEM1024-P384']) {
+ if (!SubtleCrypto.supports('generateKey', name)) continue;
+ const { privateKey, publicKey } = await subtle.generateKey(
+ name, true, ['encapsulateBits', 'decapsulateBits']);
+ const seed = await subtle.exportKey('raw-seed', privateKey);
+ const publicBytes = await subtle.exportKey('raw-public', publicKey);
+ const descriptor = Object.getOwnPropertyDescriptor(Uint8Array, Symbol.species);
+ try {
+ Object.defineProperty(Uint8Array, Symbol.species, {
+ configurable: true, get: common.mustNotCall(),
+ });
+ const imported = await subtle.importKey('raw-seed', seed, name, true,
+ ['decapsulateBits']);
+ assert.deepStrictEqual(await subtle.exportKey('raw-seed', imported), seed);
+ const publicImported = await subtle.importKey('raw-public', publicBytes, name,
+ true, ['encapsulateBits']);
+ assert.deepStrictEqual(await subtle.exportKey('raw-public', publicImported), publicBytes);
+ } finally {
+ if (descriptor) {
+ Object.defineProperty(Uint8Array, Symbol.species, descriptor);
+ } else {
+ delete Uint8Array[Symbol.species];
+ }
+ }
+ }
+})().then(common.mustCall());