• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals
2'use strict';
3
4const common = require('../common');
5if (!common.hasCrypto)
6  common.skip('missing crypto');
7
8const assert = require('assert');
9
10const webidl = require('internal/crypto/webidl');
11const { subtle } = require('node:crypto').webcrypto;
12const { generateKeySync } = require('crypto');
13
14const { converters } = webidl;
15const prefix = "Failed to execute 'fn' on 'interface'";
16const context = '1st argument';
17const opts = { prefix, context };
18
19// Required arguments.length
20{
21  assert.throws(() => webidl.requiredArguments(0, 3, { prefix }), {
22    code: 'ERR_MISSING_ARGS',
23    name: 'TypeError',
24    message: `${prefix}: 3 arguments required, but only 0 present.`
25  });
26
27  assert.throws(() => webidl.requiredArguments(0, 1, { prefix }), {
28    code: 'ERR_MISSING_ARGS',
29    name: 'TypeError',
30    message: `${prefix}: 1 argument required, but only 0 present.`
31  });
32
33  // Does not throw when extra are added
34  webidl.requiredArguments(4, 3, { prefix });
35}
36
37// boolean
38{
39  assert.strictEqual(converters.boolean(0), false);
40  assert.strictEqual(converters.boolean(NaN), false);
41  assert.strictEqual(converters.boolean(undefined), false);
42  assert.strictEqual(converters.boolean(null), false);
43  assert.strictEqual(converters.boolean(false), false);
44  assert.strictEqual(converters.boolean(''), false);
45
46  assert.strictEqual(converters.boolean(1), true);
47  assert.strictEqual(converters.boolean(Number.POSITIVE_INFINITY), true);
48  assert.strictEqual(converters.boolean(Number.NEGATIVE_INFINITY), true);
49  assert.strictEqual(converters.boolean('1'), true);
50  assert.strictEqual(converters.boolean('0'), true);
51  assert.strictEqual(converters.boolean('false'), true);
52  assert.strictEqual(converters.boolean(function() {}), true);
53  assert.strictEqual(converters.boolean(Symbol()), true);
54  assert.strictEqual(converters.boolean([]), true);
55  assert.strictEqual(converters.boolean({}), true);
56}
57
58// int conversion
59// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
60{
61  for (const [converter, max] of [
62    [converters.octet, Math.pow(2, 8) - 1],
63    [converters['unsigned short'], Math.pow(2, 16) - 1],
64    [converters['unsigned long'], Math.pow(2, 32) - 1],
65  ]) {
66    assert.strictEqual(converter(0), 0);
67    assert.strictEqual(converter(max), max);
68    assert.strictEqual(converter('' + 0), 0);
69    assert.strictEqual(converter('' + max), max);
70    assert.strictEqual(converter(3), 3);
71    assert.strictEqual(converter('' + 3), 3);
72    assert.strictEqual(converter(3.1), 3);
73    assert.strictEqual(converter(3.7), 3);
74
75    assert.strictEqual(converter(max + 1), 0);
76    assert.strictEqual(converter(max + 2), 1);
77    assert.throws(() => converter(max + 1, { ...opts, enforceRange: true }), {
78      name: 'TypeError',
79      code: 'ERR_OUT_OF_RANGE',
80      message: `${prefix}: ${context} is outside the expected range of 0 to ${max}.`,
81    });
82
83    assert.strictEqual(converter({}), 0);
84    assert.strictEqual(converter(NaN), 0);
85    assert.strictEqual(converter(false), 0);
86    assert.strictEqual(converter(true), 1);
87    assert.strictEqual(converter('1'), 1);
88    assert.strictEqual(converter('0'), 0);
89    assert.strictEqual(converter('{}'), 0);
90    assert.strictEqual(converter({}), 0);
91    assert.strictEqual(converter([]), 0);
92    assert.strictEqual(converter(function() {}), 0);
93
94    assert.throws(() => converter(Symbol(), opts), {
95      name: 'TypeError',
96      code: 'ERR_INVALID_ARG_TYPE',
97      message: `${prefix}: ${context} is a Symbol and cannot be converted to a number.`
98    });
99    assert.throws(() => converter(0n, opts), {
100      name: 'TypeError',
101      code: 'ERR_INVALID_ARG_TYPE',
102      message: `${prefix}: ${context} is a BigInt and cannot be converted to a number.`
103    });
104  }
105}
106
107// DOMString
108{
109  assert.strictEqual(converters.DOMString(1), '1');
110  assert.strictEqual(converters.DOMString(1n), '1');
111  assert.strictEqual(converters.DOMString(false), 'false');
112  assert.strictEqual(converters.DOMString(true), 'true');
113  assert.strictEqual(converters.DOMString(undefined), 'undefined');
114  assert.strictEqual(converters.DOMString(NaN), 'NaN');
115  assert.strictEqual(converters.DOMString({}), '[object Object]');
116  assert.strictEqual(converters.DOMString({ foo: 'bar' }), '[object Object]');
117  assert.strictEqual(converters.DOMString([]), '');
118  assert.strictEqual(converters.DOMString([1, 2]), '1,2');
119
120  assert.throws(() => converters.DOMString(Symbol(), opts), {
121    name: 'TypeError',
122    code: 'ERR_INVALID_ARG_TYPE',
123    message: `${prefix}: ${context} is a Symbol and cannot be converted to a string.`
124  });
125}
126
127// object
128{
129  for (const good of [{}, [], new Array(), function() {}]) {
130    assert.deepStrictEqual(converters.object(good), good);
131  }
132
133  for (const bad of [undefined, null, NaN, false, true, 0, 1, '', 'foo', Symbol(), 9n]) {
134    assert.throws(() => converters.object(bad, opts), {
135      name: 'TypeError',
136      code: 'ERR_INVALID_ARG_TYPE',
137      message: `${prefix}: ${context} is not an object.`
138    });
139  }
140}
141
142// Uint8Array
143{
144  for (const good of [Buffer.alloc(0), new Uint8Array()]) {
145    assert.deepStrictEqual(converters.Uint8Array(good), good);
146  }
147
148  for (const bad of [new ArrayBuffer(), new SharedArrayBuffer(), [], null, 'foo', undefined, true]) {
149    assert.throws(() => converters.Uint8Array(bad, opts), {
150      name: 'TypeError',
151      code: 'ERR_INVALID_ARG_TYPE',
152      message: `${prefix}: ${context} is not an Uint8Array object.`
153    });
154  }
155
156  assert.throws(() => converters.Uint8Array(new Uint8Array(new SharedArrayBuffer()), opts), {
157    name: 'TypeError',
158    code: 'ERR_INVALID_ARG_TYPE',
159    message: `${prefix}: ${context} is a view on a SharedArrayBuffer, which is not allowed.`
160  });
161}
162
163// BufferSource
164{
165  for (const good of [
166    Buffer.alloc(0),
167    new Uint8Array(),
168    new ArrayBuffer(),
169    new DataView(new ArrayBuffer()),
170    new BigInt64Array(),
171    new BigUint64Array(),
172    new Float32Array(),
173    new Float64Array(),
174    new Int8Array(),
175    new Int16Array(),
176    new Int32Array(),
177    new Uint8ClampedArray(),
178    new Uint16Array(),
179    new Uint32Array(),
180  ]) {
181    assert.deepStrictEqual(converters.BufferSource(good), good);
182  }
183
184  for (const bad of [new SharedArrayBuffer(), [], null, 'foo', undefined, true]) {
185    assert.throws(() => converters.BufferSource(bad, opts), {
186      name: 'TypeError',
187      code: 'ERR_INVALID_ARG_TYPE',
188      message: `${prefix}: ${context} is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.`
189    });
190  }
191
192  assert.throws(() => converters.BufferSource(new Uint8Array(new SharedArrayBuffer()), opts), {
193    name: 'TypeError',
194    code: 'ERR_INVALID_ARG_TYPE',
195    message: `${prefix}: ${context} is a view on a SharedArrayBuffer, which is not allowed.`
196  });
197}
198
199// CryptoKey
200{
201
202  subtle.generateKey({ name: 'AES-CBC', length: 128 }, false, ['encrypt']).then((key) => {
203    assert.deepStrictEqual(converters.CryptoKey(key), key);
204  }).then(common.mustCall());
205
206  for (const bad of [
207    generateKeySync('aes', { length: 128 }),
208    undefined, null, 1, {}, Symbol(), true, false, [],
209  ]) {
210    assert.throws(() => converters.CryptoKey(bad, opts), {
211      name: 'TypeError',
212      code: 'ERR_INVALID_ARG_TYPE',
213      message: `${prefix}: ${context} is not of type CryptoKey.`
214    });
215  }
216}
217
218// AlgorithmIdentifier (Union for (object or DOMString))
219{
220  assert.strictEqual(converters.AlgorithmIdentifier('foo'), 'foo');
221  assert.deepStrictEqual(converters.AlgorithmIdentifier({ name: 'foo' }), { name: 'foo' });
222}
223
224// JsonWebKey
225{
226  for (const good of [
227    {},
228    { use: 'sig' },
229    { key_ops: ['sign'] },
230    { ext: true },
231    { oth: [] },
232    { oth: [{ r: '', d: '', t: '' }] },
233  ]) {
234    assert.deepStrictEqual(converters.JsonWebKey(good), good);
235    assert.deepStrictEqual(converters.JsonWebKey({ ...good, filtered: 'out' }), good);
236  }
237}
238
239// KeyFormat
240{
241  for (const good of ['jwk', 'spki', 'pkcs8', 'raw']) {
242    assert.strictEqual(converters.KeyFormat(good), good);
243  }
244
245  for (const bad of ['foo', 1, false]) {
246    assert.throws(() => converters.KeyFormat(bad, opts), {
247      name: 'TypeError',
248      code: 'ERR_INVALID_ARG_VALUE',
249      message: `${prefix}: ${context} value '${bad}' is not a valid enum value of type KeyFormat.`,
250    });
251  }
252}
253
254// KeyUsage
255{
256  for (const good of [
257    'encrypt',
258    'decrypt',
259    'sign',
260    'verify',
261    'deriveKey',
262    'deriveBits',
263    'wrapKey',
264    'unwrapKey',
265  ]) {
266    assert.strictEqual(converters.KeyUsage(good), good);
267  }
268
269  for (const bad of ['foo', 1, false]) {
270    assert.throws(() => converters.KeyUsage(bad, opts), {
271      name: 'TypeError',
272      code: 'ERR_INVALID_ARG_VALUE',
273      message: `${prefix}: ${context} value '${bad}' is not a valid enum value of type KeyUsage.`,
274    });
275  }
276}
277
278// Algorithm
279{
280  const good = { name: 'RSA-PSS' };
281  assert.deepStrictEqual(converters.Algorithm({ ...good, filtered: 'out' }, opts), good);
282
283  assert.throws(() => converters.Algorithm({}, opts), {
284    name: 'TypeError',
285    code: 'ERR_MISSING_OPTION',
286    message: `${prefix}: ${context} can not be converted to 'Algorithm' because 'name' is required in 'Algorithm'.`,
287  });
288}
289
290// RsaHashedKeyGenParams
291{
292  for (const good of [
293    {
294      name: 'RSA-OAEP',
295      hash: { name: 'SHA-1' },
296      modulusLength: 2048,
297      publicExponent: new Uint8Array([1, 0, 1]),
298    },
299    {
300      name: 'RSA-OAEP',
301      hash: 'SHA-1',
302      modulusLength: 2048,
303      publicExponent: new Uint8Array([1, 0, 1]),
304    },
305  ]) {
306    assert.deepStrictEqual(converters.RsaHashedKeyGenParams({ ...good, filtered: 'out' }, opts), good);
307    for (const required of ['hash', 'publicExponent', 'modulusLength']) {
308      assert.throws(() => converters.RsaHashedKeyGenParams({ ...good, [required]: undefined }, opts), {
309        name: 'TypeError',
310        code: 'ERR_MISSING_OPTION',
311        message: `${prefix}: ${context} can not be converted to 'RsaHashedKeyGenParams' because '${required}' is required in 'RsaHashedKeyGenParams'.`,
312      });
313    }
314  }
315}
316
317// RsaHashedImportParams
318{
319  for (const good of [
320    { name: 'RSA-OAEP', hash: { name: 'SHA-1' } },
321    { name: 'RSA-OAEP', hash: 'SHA-1' },
322  ]) {
323    assert.deepStrictEqual(converters.RsaHashedImportParams({ ...good, filtered: 'out' }, opts), good);
324    assert.throws(() => converters.RsaHashedImportParams({ ...good, hash: undefined }, opts), {
325      name: 'TypeError',
326      code: 'ERR_MISSING_OPTION',
327      message: `${prefix}: ${context} can not be converted to 'RsaHashedImportParams' because 'hash' is required in 'RsaHashedImportParams'.`,
328    });
329  }
330}
331
332// RsaPssParams
333{
334  const good = { name: 'RSA-PSS', saltLength: 20 };
335  assert.deepStrictEqual(converters.RsaPssParams({ ...good, filtered: 'out' }, opts), good);
336
337  assert.throws(() => converters.RsaPssParams({ ...good, saltLength: undefined }, opts), {
338    name: 'TypeError',
339    code: 'ERR_MISSING_OPTION',
340    message: `${prefix}: ${context} can not be converted to 'RsaPssParams' because 'saltLength' is required in 'RsaPssParams'.`,
341  });
342}
343
344// RsaOaepParams
345{
346  for (const good of [{ name: 'RSA-OAEP' }, { name: 'RSA-OAEP', label: Buffer.alloc(0) }]) {
347    assert.deepStrictEqual(converters.RsaOaepParams({ ...good, filtered: 'out' }, opts), good);
348  }
349}
350
351// EcKeyImportParams, EcKeyGenParams
352{
353  for (const name of ['EcKeyImportParams', 'EcKeyGenParams']) {
354    const { [name]: converter } = converters;
355
356    const good = { name: 'ECDSA', namedCurve: 'P-256' };
357    assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good);
358
359    assert.throws(() => converter({ ...good, namedCurve: undefined }, opts), {
360      name: 'TypeError',
361      code: 'ERR_MISSING_OPTION',
362      message: `${prefix}: ${context} can not be converted to '${name}' because 'namedCurve' is required in '${name}'.`,
363    });
364  }
365}
366
367// EcdsaParams
368{
369  for (const good of [
370    { name: 'ECDSA', hash: { name: 'SHA-1' } },
371    { name: 'ECDSA', hash: 'SHA-1' },
372  ]) {
373    assert.deepStrictEqual(converters.EcdsaParams({ ...good, filtered: 'out' }, opts), good);
374    assert.throws(() => converters.EcdsaParams({ ...good, hash: undefined }, opts), {
375      name: 'TypeError',
376      code: 'ERR_MISSING_OPTION',
377      message: `${prefix}: ${context} can not be converted to 'EcdsaParams' because 'hash' is required in 'EcdsaParams'.`,
378    });
379  }
380}
381
382// HmacKeyGenParams, HmacImportParams
383{
384  for (const name of ['HmacKeyGenParams', 'HmacImportParams']) {
385    const { [name]: converter } = converters;
386
387    for (const good of [
388      { name: 'HMAC', hash: { name: 'SHA-1' } },
389      { name: 'HMAC', hash: { name: 'SHA-1' }, length: 20 },
390      { name: 'HMAC', hash: 'SHA-1' },
391      { name: 'HMAC', hash: 'SHA-1', length: 20 },
392    ]) {
393      assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good);
394      assert.throws(() => converter({ ...good, hash: undefined }, opts), {
395        name: 'TypeError',
396        code: 'ERR_MISSING_OPTION',
397        message: `${prefix}: ${context} can not be converted to '${name}' because 'hash' is required in '${name}'.`,
398      });
399    }
400  }
401}
402
403// AesKeyGenParams, AesDerivedKeyParams
404{
405  for (const name of ['AesKeyGenParams', 'AesDerivedKeyParams']) {
406    const { [name]: converter } = converters;
407
408    const good = { name: 'AES-CBC', length: 128 };
409    assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good);
410
411    assert.throws(() => converter({ ...good, length: undefined }, opts), {
412      name: 'TypeError',
413      code: 'ERR_MISSING_OPTION',
414      message: `${prefix}: ${context} can not be converted to '${name}' because 'length' is required in '${name}'.`,
415    });
416  }
417}
418
419// HkdfParams
420{
421  for (const good of [
422    { name: 'HKDF', hash: { name: 'SHA-1' }, salt: Buffer.alloc(0), info: Buffer.alloc(0) },
423    { name: 'HKDF', hash: 'SHA-1', salt: Buffer.alloc(0), info: Buffer.alloc(0) },
424  ]) {
425    assert.deepStrictEqual(converters.HkdfParams({ ...good, filtered: 'out' }, opts), good);
426    for (const required of ['hash', 'salt', 'info']) {
427      assert.throws(() => converters.HkdfParams({ ...good, [required]: undefined }, opts), {
428        name: 'TypeError',
429        code: 'ERR_MISSING_OPTION',
430        message: `${prefix}: ${context} can not be converted to 'HkdfParams' because '${required}' is required in 'HkdfParams'.`,
431      });
432    }
433  }
434}
435
436// Pbkdf2Params
437{
438  for (const good of [
439    { name: 'PBKDF2', hash: { name: 'SHA-1' }, iterations: 5, salt: Buffer.alloc(0) },
440    { name: 'PBKDF2', hash: 'SHA-1', iterations: 5, salt: Buffer.alloc(0) },
441  ]) {
442    assert.deepStrictEqual(converters.Pbkdf2Params({ ...good, filtered: 'out' }, opts), good);
443    for (const required of ['hash', 'iterations', 'salt']) {
444      assert.throws(() => converters.Pbkdf2Params({ ...good, [required]: undefined }, opts), {
445        name: 'TypeError',
446        code: 'ERR_MISSING_OPTION',
447        message: `${prefix}: ${context} can not be converted to 'Pbkdf2Params' because '${required}' is required in 'Pbkdf2Params'.`,
448      });
449    }
450  }
451}
452
453// AesCbcParams
454{
455  const good = { name: 'AES-CBC', iv: Buffer.alloc(0) };
456  assert.deepStrictEqual(converters.AesCbcParams({ ...good, filtered: 'out' }, opts), good);
457
458  assert.throws(() => converters.AesCbcParams({ ...good, iv: undefined }, opts), {
459    name: 'TypeError',
460    code: 'ERR_MISSING_OPTION',
461    message: `${prefix}: ${context} can not be converted to 'AesCbcParams' because 'iv' is required in 'AesCbcParams'.`,
462  });
463}
464
465// AesGcmParams
466{
467  for (const good of [
468    { name: 'AES-GCM', iv: Buffer.alloc(0) },
469    { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 16 },
470    { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 16, additionalData: Buffer.alloc(0) },
471  ]) {
472    assert.deepStrictEqual(converters.AesGcmParams({ ...good, filtered: 'out' }, opts), good);
473
474    assert.throws(() => converters.AesGcmParams({ ...good, iv: undefined }, opts), {
475      name: 'TypeError',
476      code: 'ERR_MISSING_OPTION',
477      message: `${prefix}: ${context} can not be converted to 'AesGcmParams' because 'iv' is required in 'AesGcmParams'.`,
478    });
479  }
480}
481
482// AesCtrParams
483{
484  const good = { name: 'AES-CTR', counter: Buffer.alloc(0), length: 20 };
485  assert.deepStrictEqual(converters.AesCtrParams({ ...good, filtered: 'out' }, opts), good);
486
487  for (const required of ['counter', 'length']) {
488    assert.throws(() => converters.AesCtrParams({ ...good, [required]: undefined }, opts), {
489      name: 'TypeError',
490      code: 'ERR_MISSING_OPTION',
491      message: `${prefix}: ${context} can not be converted to 'AesCtrParams' because '${required}' is required in 'AesCtrParams'.`,
492    });
493  }
494}
495
496// EcdhKeyDeriveParams
497{
498  subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits']).then((kp) => {
499    const good = { name: 'ECDH', public: kp.publicKey };
500    assert.deepStrictEqual(converters.EcdhKeyDeriveParams({ ...good, filtered: 'out' }, opts), good);
501
502    assert.throws(() => converters.EcdhKeyDeriveParams({ ...good, public: undefined }, opts), {
503      name: 'TypeError',
504      code: 'ERR_MISSING_OPTION',
505      message: `${prefix}: ${context} can not be converted to 'EcdhKeyDeriveParams' because 'public' is required in 'EcdhKeyDeriveParams'.`,
506    });
507  }).then(common.mustCall());
508}
509
510// Ed448Params
511{
512  for (const good of [
513    { name: 'Ed448', context: new Uint8Array() },
514    { name: 'Ed448' },
515  ]) {
516    assert.deepStrictEqual(converters.Ed448Params({ ...good, filtered: 'out' }, opts), good);
517  }
518}
519