Skip to content

Commit da7c40e

Browse files
committed
fixup! test: add CryptoKey class regression tests
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent ef0c779 commit da7c40e

5 files changed

Lines changed: 176 additions & 8 deletions

test/parallel/test-webcrypto-cryptokey-brand-check.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ const { subtle } = globalThis.crypto;
8686
Object.setPrototypeOf(spoofed, Object.getPrototypeOf(key));
8787
assert.strictEqual(spoofed instanceof CryptoKey, true);
8888
assert.strictEqual(isCryptoKey(spoofed), false);
89+
await assert.rejects(
90+
subtle.sign('HMAC', spoofed, Buffer.from('payload')),
91+
invalidThis);
92+
await assert.rejects(
93+
subtle.exportKey('jwk', spoofed),
94+
invalidThis);
8995

9096
// Subvert `instanceof CryptoKey` via Symbol.hasInstance, then
9197
// invoke the native getters on a forged object. The C++ tag

test/parallel/test-webcrypto-cryptokey-clone-transfer.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,34 @@ const { once } = require('node:events');
2222
const { Worker, MessageChannel } = require('node:worker_threads');
2323
const { subtle } = globalThis.crypto;
2424

25+
function assertNoOwnReflection(key) {
26+
assert.deepStrictEqual(Object.getOwnPropertySymbols(key), []);
27+
assert.deepStrictEqual(Object.getOwnPropertyNames(key), []);
28+
assert.deepStrictEqual(Reflect.ownKeys(key), []);
29+
}
30+
31+
function describeKey(key) {
32+
const algorithm = { ...key.algorithm };
33+
if (algorithm.hash !== undefined)
34+
algorithm.hash = { ...algorithm.hash };
35+
if (algorithm.publicExponent !== undefined)
36+
algorithm.publicExponent = Array.from(algorithm.publicExponent);
37+
return {
38+
type: key.type,
39+
extractable: key.extractable,
40+
algorithm,
41+
usages: [...key.usages].sort(),
42+
};
43+
}
44+
2545
function assertSameCryptoKey(a, b) {
2646
assert.notStrictEqual(a, b);
2747
assert.strictEqual(a.type, b.type);
2848
assert.strictEqual(a.extractable, b.extractable);
2949
assert.deepStrictEqual(a.algorithm, b.algorithm);
3050
assert.deepStrictEqual([...a.usages].sort(), [...b.usages].sort());
51+
assertNoOwnReflection(a);
52+
assertNoOwnReflection(b);
3153
// util.inspect reads native internal slots directly, so a clone's
3254
// rendered form must match the original's.
3355
assert.strictEqual(inspect(a, { depth: 4 }), inspect(b, { depth: 4 }));
@@ -191,6 +213,83 @@ async function checkTransferToWorker(key) {
191213
true);
192214
}
193215

216+
async function checkRsaPssTransferToWorker({ publicKey, privateKey }) {
217+
const data = Buffer.from('rsa-pss worker payload');
218+
const algorithm = { name: 'RSA-PSS', saltLength: 32 };
219+
const parentSignature = Buffer.from(
220+
await subtle.sign(algorithm, privateKey, data));
221+
222+
const worker = new Worker(`
223+
'use strict';
224+
const assert = require('node:assert');
225+
const { parentPort } = require('node:worker_threads');
226+
const { subtle } = globalThis.crypto;
227+
228+
function describeKey(key) {
229+
const algorithm = { ...key.algorithm };
230+
if (algorithm.hash !== undefined)
231+
algorithm.hash = { ...algorithm.hash };
232+
if (algorithm.publicExponent !== undefined)
233+
algorithm.publicExponent = Array.from(algorithm.publicExponent);
234+
return {
235+
type: key.type,
236+
extractable: key.extractable,
237+
algorithm,
238+
usages: [...key.usages].sort(),
239+
};
240+
}
241+
242+
parentPort.once('message', async (message) => {
243+
try {
244+
const {
245+
publicKey,
246+
privateKey,
247+
expectedPublic,
248+
expectedPrivate,
249+
signature,
250+
data,
251+
} = message;
252+
assert.deepStrictEqual(describeKey(publicKey), expectedPublic);
253+
assert.deepStrictEqual(describeKey(privateKey), expectedPrivate);
254+
255+
const algorithm = { name: 'RSA-PSS', saltLength: 32 };
256+
const verified = await subtle.verify(
257+
algorithm, publicKey, signature, data);
258+
const workerSignature = Buffer.from(
259+
await subtle.sign(algorithm, privateKey, data));
260+
261+
parentPort.postMessage({
262+
publicKey,
263+
privateKey,
264+
verified,
265+
signature: workerSignature,
266+
});
267+
} catch (err) {
268+
parentPort.postMessage({ error: err.stack || err.message });
269+
}
270+
});
271+
`, { eval: true });
272+
273+
worker.postMessage({
274+
publicKey,
275+
privateKey,
276+
expectedPublic: describeKey(publicKey),
277+
expectedPrivate: describeKey(privateKey),
278+
signature: parentSignature,
279+
data,
280+
});
281+
const [msg] = await once(worker, 'message');
282+
await worker.terminate();
283+
284+
assert.strictEqual(msg.error, undefined, msg.error);
285+
assert.strictEqual(msg.verified, true);
286+
assertSameCryptoKey(publicKey, msg.publicKey);
287+
assertSameCryptoKey(privateKey, msg.privateKey);
288+
assert.strictEqual(
289+
await subtle.verify(algorithm, publicKey, msg.signature, data),
290+
true);
291+
}
292+
194293
(async () => {
195294
// Extractable HMAC (secret)
196295
const hmacExtractable = await subtle.importKey(
@@ -248,4 +347,17 @@ async function checkTransferToWorker(key) {
248347
true,
249348
['sign', 'verify']);
250349
await checkAsymmetricKeyPair(ecKeypairExtractable);
350+
351+
// RSA-PSS keypair through a Worker (covers public/private native key
352+
// handles and cloning of the publicExponent algorithm member).
353+
const rsaPssKeypair = await subtle.generateKey(
354+
{
355+
name: 'RSA-PSS',
356+
modulusLength: 2048,
357+
publicExponent: new Uint8Array([1, 0, 1]),
358+
hash: 'SHA-256',
359+
},
360+
false,
361+
['sign', 'verify']);
362+
await checkRsaPssTransferToWorker(rsaPssKeypair);
251363
})().then(common.mustCall());

test/parallel/test-webcrypto-cryptokey-hidden-slots.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,42 @@
1111
//
1212
// 1. util.inspect() shows the real internal values, unaffected by
1313
// the replacement getters.
14-
// 2. Internal operations (export) that receive the mutated CryptoKey
15-
// still succeed and observe the real internal state, not the
16-
// replaced one.
14+
// 2. Internal Web Crypto and Node.js crypto bridge operations that
15+
// receive the mutated CryptoKey still succeed and observe the real
16+
// internal state, not the replaced one.
1717

1818
const common = require('../common');
1919
if (!common.hasCrypto)
2020
common.skip('missing crypto');
2121

2222
const assert = require('node:assert');
23+
const {
24+
createHmac,
25+
KeyObject,
26+
sign: cryptoSign,
27+
verify: cryptoVerify,
28+
} = require('node:crypto');
2329
const { inspect } = require('node:util');
2430
const { subtle } = globalThis.crypto;
2531

32+
common.expectWarning({
33+
DeprecationWarning: {
34+
DEP0203: 'Passing a CryptoKey to node:crypto functions is deprecated.',
35+
},
36+
});
37+
2638
(async () => {
2739
const key = await subtle.generateKey(
2840
{ name: 'HMAC', hash: 'SHA-256' },
2941
true,
3042
['sign', 'verify'],
3143
);
44+
const { publicKey: ecPublicKey, privateKey: ecPrivateKey } =
45+
await subtle.generateKey(
46+
{ name: 'ECDSA', namedCurve: 'P-256' },
47+
false,
48+
['sign', 'verify'],
49+
);
3250

3351
// Snapshot the real values BEFORE tampering.
3452
const realType = key.type;
@@ -99,7 +117,25 @@ const { subtle } = globalThis.crypto;
99117
assert.strictEqual(jwk.ext, true);
100118
assert.deepStrictEqual(jwk.key_ops.sort(), ['sign', 'verify']);
101119

102-
// 4) Importing back from the exported JWK must yield an equivalent
120+
// 4) The Node.js crypto bridge must also read the real native
121+
// slots directly, both for KeyObject.from() and for deprecated
122+
// direct CryptoKey consumption.
123+
const keyObject = KeyObject.from(key);
124+
assert.strictEqual(keyObject.type, 'secret');
125+
assert.deepStrictEqual(keyObject.export(), Buffer.from(jwk.k, 'base64url'));
126+
127+
const payload = Buffer.from('payload');
128+
const digest = createHmac('sha256', key).update(payload).digest('hex');
129+
const expectedDigest =
130+
createHmac('sha256', keyObject).update(payload).digest('hex');
131+
assert.strictEqual(digest, expectedDigest);
132+
133+
const signature = cryptoSign('sha256', payload, ecPrivateKey);
134+
assert.strictEqual(
135+
cryptoVerify('sha256', payload, ecPublicKey, signature),
136+
true);
137+
138+
// 5) Importing back from the exported JWK must yield an equivalent
103139
// key, i.e. the real algorithm and usages round-trip.
104140
const reimported = await subtle.importKey('jwk', jwk,
105141
{ name: 'HMAC', hash: 'SHA-256' },

test/parallel/test-webcrypto-cryptokey-no-own-symbols.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
// CryptoKey instances must not expose any own Symbol-keyed properties
44
// to user code. The internal slots backing the public getters are
5-
// kept off-instance (in a module-local WeakMap) so that reflection
6-
// APIs such as Object.getOwnPropertySymbols() and Reflect.ownKeys()
7-
// cannot enumerate them, even after the public getters have been
8-
// invoked and their per-instance caches populated.
5+
// kept in native internal fields, with JS-side caches in private fields,
6+
// so that reflection APIs such as Object.getOwnPropertySymbols() and
7+
// Reflect.ownKeys() cannot enumerate them, even after the public getters
8+
// have been invoked and their per-instance caches populated.
99

1010
const common = require('../common');
1111
if (!common.hasCrypto)

test/parallel/test-webcrypto-export-import-rsa.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,20 @@ async function testImportJwk(
600600
extractable,
601601
publicUsages),
602602
{ name: 'DataError', message: 'Invalid keyData' });
603+
604+
for (const field of ['p', 'q', 'dp', 'dq', 'qi']) {
605+
const jwkMissingCrtField = { ...jwk };
606+
delete jwkMissingCrtField[field];
607+
await assert.rejects(
608+
subtle.importKey(
609+
'jwk',
610+
jwkMissingCrtField,
611+
{ name, hash },
612+
extractable,
613+
privateUsages),
614+
{ name: 'DataError', message: 'Invalid keyData' },
615+
`missing private JWK CRT field ${field}`);
616+
}
603617
}
604618

605619
// combinations to test

0 commit comments

Comments
 (0)