Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
SafeSet,
StringPrototypeToLowerCase,
} = primordials;

const { Buffer } = require('buffer');
Expand Down Expand Up @@ -332,7 +333,7 @@ function cfrgImportKey(
return undefined;
}

if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
if (keyObject.asymmetricKeyType !== StringPrototypeToLowerCase(name)) {
throw lazyDOMException('Invalid key type', 'DataError');
}

Expand Down
3 changes: 2 additions & 1 deletion lib/internal/crypto/ml_dsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
SafeSet,
StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeSet,
Uint8Array,
Expand Down Expand Up @@ -276,7 +277,7 @@ function mlDsaImportKey(
return undefined;
}

if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
if (keyObject.asymmetricKeyType !== StringPrototypeToLowerCase(name)) {
throw lazyDOMException('Invalid key type', 'DataError');
}

Expand Down
3 changes: 2 additions & 1 deletion lib/internal/crypto/ml_kem.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const {
PromiseWithResolvers,
SafeSet,
StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeSet,
Uint8Array,
Expand Down Expand Up @@ -209,7 +210,7 @@ function mlKemImportKey(
return undefined;
}

if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
if (keyObject.asymmetricKeyType !== StringPrototypeToLowerCase(name)) {
throw lazyDOMException('Invalid key type', 'DataError');
}

Expand Down
20 changes: 10 additions & 10 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const {
ObjectEntries,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
Promise,
PromiseWithResolvers,
StringPrototypeToUpperCase,
Symbol,
TypedArrayPrototypeGetBuffer,
Expand Down Expand Up @@ -656,15 +656,15 @@ function onDone(resolve, reject, err, result) {
}

function jobPromise(getJob) {
return new Promise((resolve, reject) => {
try {
const job = getJob();
job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject);
job.run();
} catch (err) {
onDone(resolve, reject, err);
}
});
const { promise, resolve, reject } = PromiseWithResolvers();
try {
const job = getJob();
job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject);
job.run();
} catch (err) {
onDone(resolve, reject, err);
}
return promise;
}

// In WebCrypto, the publicExponent option in RSA is represented as a
Expand Down
12 changes: 7 additions & 5 deletions lib/internal/crypto/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,14 @@ converters.object = (V, opts) => {

const isNonSharedArrayBuffer = isArrayBuffer;

/**
* @param {string | object} V - The hash algorithm identifier (string or object).
* @param {string} label - The dictionary name for the error message.
*/
function ensureSHA(V, label) {
if (
typeof V === 'string' ?
!StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') :
V.name?.toLowerCase?.().startsWith('sha') === false
)
const name = typeof V === 'string' ? V : V.name;
if (typeof name !== 'string' ||
!StringPrototypeStartsWith(StringPrototypeToLowerCase(name), 'sha'))
throw lazyDOMException(
`Only SHA hashes are supported in ${label}`, 'NotSupportedError');
}
Expand Down
95 changes: 95 additions & 0 deletions test/parallel/test-webcrypto-promise-prototype-pollution.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as common from '../common/index.mjs';

if (!common.hasCrypto) common.skip('missing crypto');

// WebCrypto subtle methods must not leak intermediate values
// through Promise.prototype.then pollution.
// Regression test for https://github.com/nodejs/node/pull/61492
// and https://github.com/nodejs/node/issues/59699.

import { hasOpenSSL } from '../common/crypto.js';

const { subtle } = globalThis.crypto;

Promise.prototype.then = common.mustNotCall('Promise.prototype.then');

await subtle.digest('SHA-256', new Uint8Array([1, 2, 3]));

await subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']);

await subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']);

const rawKey = globalThis.crypto.getRandomValues(new Uint8Array(32));

const importedKey = await subtle.importKey(
'raw', rawKey, { name: 'AES-CBC', length: 256 }, false, ['encrypt', 'decrypt']);

const exportableKey = await subtle.importKey(
'raw', rawKey, { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']);

await subtle.exportKey('raw', exportableKey);

const iv = globalThis.crypto.getRandomValues(new Uint8Array(16));
const plaintext = new TextEncoder().encode('Hello, world!');

const ciphertext = await subtle.encrypt({ name: 'AES-CBC', iv }, importedKey, plaintext);

await subtle.decrypt({ name: 'AES-CBC', iv }, importedKey, ciphertext);

const signingKey = await subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);

const data = new TextEncoder().encode('test data');

const signature = await subtle.sign('HMAC', signingKey, data);

await subtle.verify('HMAC', signingKey, signature, data);

const pbkdf2Key = await subtle.importKey(
'raw', rawKey, 'PBKDF2', false, ['deriveBits', 'deriveKey']);

await subtle.deriveBits(
{ name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' },
pbkdf2Key, 256);

await subtle.deriveKey(
{ name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' },
pbkdf2Key,
{ name: 'AES-CBC', length: 256 },
true,
['encrypt', 'decrypt']);

const wrappingKey = await subtle.generateKey(
{ name: 'AES-KW', length: 256 }, true, ['wrapKey', 'unwrapKey']);

const keyToWrap = await subtle.generateKey(
{ name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']);

const wrapped = await subtle.wrapKey('raw', keyToWrap, wrappingKey, 'AES-KW');

await subtle.unwrapKey(
'raw', wrapped, wrappingKey, 'AES-KW',
{ name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']);

const { privateKey } = await subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']);

await subtle.getPublicKey(privateKey, ['verify']);

if (hasOpenSSL(3, 5)) {
const kemPair = await subtle.generateKey(
{ name: 'ML-KEM-768' }, false,
['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits']);

const { ciphertext: ct1 } = await subtle.encapsulateKey(
{ name: 'ML-KEM-768' }, kemPair.publicKey, 'HKDF', false, ['deriveBits']);

await subtle.decapsulateKey(
{ name: 'ML-KEM-768' }, kemPair.privateKey, ct1, 'HKDF', false, ['deriveBits']);

const { ciphertext: ct2 } = await subtle.encapsulateBits(
{ name: 'ML-KEM-768' }, kemPair.publicKey);

await subtle.decapsulateBits(
{ name: 'ML-KEM-768' }, kemPair.privateKey, ct2);
}
Loading