xref: /third_party/node/lib/internal/crypto/webidl.js (revision 1cb0ef41)
1'use strict';
2
3// Adapted from the following sources
4// - https://github.com/jsdom/webidl-conversions
5//   Copyright Domenic Denicola. Licensed under BSD-2-Clause License.
6//   Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md.
7// - https://github.com/denoland/deno
8//   Copyright Deno authors. Licensed under MIT License.
9//   Original license at https://github.com/denoland/deno/blob/main/LICENSE.md.
10// Changes include using primordials and stripping the code down to only what
11// WebCryptoAPI needs.
12
13const {
14  ArrayBufferIsView,
15  ArrayBufferPrototype,
16  ArrayPrototypePush,
17  ArrayPrototypeSort,
18  MathPow,
19  MathTrunc,
20  Number,
21  NumberIsFinite,
22  ObjectAssign,
23  ObjectPrototypeIsPrototypeOf,
24  SafeArrayIterator,
25  SafeSet,
26  String,
27  SymbolIterator,
28  TypedArrayPrototypeGetBuffer,
29  TypedArrayPrototypeGetSymbolToStringTag,
30  TypeError,
31  globalThis: {
32    SharedArrayBuffer,
33  },
34} = primordials;
35
36const {
37  kEmptyObject,
38  setOwnProperty,
39} = require('internal/util');
40const { CryptoKey } = require('internal/crypto/webcrypto');
41const { getDataViewOrTypedArrayBuffer } = require('internal/crypto/util');
42
43function codedTypeError(message, errorProperties = kEmptyObject) {
44  // eslint-disable-next-line no-restricted-syntax
45  const err = new TypeError(message);
46  ObjectAssign(err, errorProperties);
47  return err;
48}
49
50function makeException(message, opts = kEmptyObject) {
51  const prefix = opts.prefix ? opts.prefix + ': ' : '';
52  const context = opts.context?.length === 0 ?
53    '' : (opts.context ?? 'Value') + ' ';
54  return codedTypeError(
55    `${prefix}${context}${message}`,
56    { code: opts.code || 'ERR_INVALID_ARG_TYPE' },
57  );
58}
59
60// https://tc39.es/ecma262/#sec-tonumber
61function toNumber(value, opts = kEmptyObject) {
62  switch (typeof value) {
63    case 'number':
64      return value;
65    case 'bigint':
66      throw makeException(
67        'is a BigInt and cannot be converted to a number.',
68        opts);
69    case 'symbol':
70      throw makeException(
71        'is a Symbol and cannot be converted to a number.',
72        opts);
73    default:
74      return Number(value);
75  }
76}
77
78function type(V) {
79  if (V === null)
80    return 'Null';
81
82  switch (typeof V) {
83    case 'undefined':
84      return 'Undefined';
85    case 'boolean':
86      return 'Boolean';
87    case 'number':
88      return 'Number';
89    case 'string':
90      return 'String';
91    case 'symbol':
92      return 'Symbol';
93    case 'bigint':
94      return 'BigInt';
95    case 'object': // Fall through
96    case 'function': // Fall through
97    default:
98      // Per ES spec, typeof returns an implemention-defined value that is not
99      // any of the existing ones for uncallable non-standard exotic objects.
100      // Yet Type() which the Web IDL spec depends on returns Object for such
101      // cases. So treat the default case as an object.
102      return 'Object';
103  }
104}
105
106const integerPart = MathTrunc;
107
108// This was updated to only consider bitlength up to 32 used by WebCryptoAPI
109function createIntegerConversion(bitLength) {
110  const lowerBound = 0;
111  const upperBound = MathPow(2, bitLength) - 1;
112
113  const twoToTheBitLength = MathPow(2, bitLength);
114
115  return (V, opts = kEmptyObject) => {
116    let x = toNumber(V, opts);
117
118    if (opts.enforceRange) {
119      if (!NumberIsFinite(x)) {
120        throw makeException(
121          'is not a finite number.',
122          opts);
123      }
124
125      x = integerPart(x);
126
127      if (x < lowerBound || x > upperBound) {
128        throw makeException(
129          `is outside the expected range of ${lowerBound} to ${upperBound}.`,
130          { __proto__: null, ...opts, code: 'ERR_OUT_OF_RANGE' },
131        );
132      }
133
134      return x;
135    }
136
137    if (!NumberIsFinite(x) || x === 0) {
138      return 0;
139    }
140
141    x = integerPart(x);
142
143    if (x >= lowerBound && x <= upperBound) {
144      return x;
145    }
146
147    x = x % twoToTheBitLength;
148
149    return x;
150  };
151}
152
153const converters = {};
154
155converters.boolean = (val) => !!val;
156converters.octet = createIntegerConversion(8);
157converters['unsigned short'] = createIntegerConversion(16);
158converters['unsigned long'] = createIntegerConversion(32);
159
160converters.DOMString = function(V, opts = kEmptyObject) {
161  if (typeof V === 'string') {
162    return V;
163  } else if (typeof V === 'symbol') {
164    throw makeException(
165      'is a Symbol and cannot be converted to a string.',
166      opts);
167  }
168
169  return String(V);
170};
171
172converters.object = (V, opts) => {
173  if (type(V) !== 'Object') {
174    throw makeException(
175      'is not an object.',
176      opts);
177  }
178
179  return V;
180};
181
182function isNonSharedArrayBuffer(V) {
183  return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V);
184}
185
186function isSharedArrayBuffer(V) {
187  return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V);
188}
189
190converters.Uint8Array = (V, opts = kEmptyObject) => {
191  if (!ArrayBufferIsView(V) ||
192    TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
193    throw makeException(
194      'is not an Uint8Array object.',
195      opts);
196  }
197  if (isSharedArrayBuffer(TypedArrayPrototypeGetBuffer(V))) {
198    throw makeException(
199      'is a view on a SharedArrayBuffer, which is not allowed.',
200      opts);
201  }
202
203  return V;
204};
205
206converters.BufferSource = (V, opts = kEmptyObject) => {
207  if (ArrayBufferIsView(V)) {
208    if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
209      throw makeException(
210        'is a view on a SharedArrayBuffer, which is not allowed.',
211        opts);
212    }
213
214    return V;
215  }
216
217  if (!isNonSharedArrayBuffer(V)) {
218    throw makeException(
219      'is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.',
220      opts);
221  }
222
223  return V;
224};
225
226converters['sequence<DOMString>'] = createSequenceConverter(
227  converters.DOMString);
228
229function requiredArguments(length, required, opts = kEmptyObject) {
230  if (length < required) {
231    throw makeException(
232      `${required} argument${
233        required === 1 ? '' : 's'
234      } required, but only ${length} present.`,
235      { __proto__: null, ...opts, context: '', code: 'ERR_MISSING_ARGS' });
236  }
237}
238
239function createDictionaryConverter(name, dictionaries) {
240  let hasRequiredKey = false;
241  const allMembers = [];
242  for (let i = 0; i < dictionaries.length; i++) {
243    const member = dictionaries[i];
244    if (member.required) {
245      hasRequiredKey = true;
246    }
247    ArrayPrototypePush(allMembers, member);
248  }
249  ArrayPrototypeSort(allMembers, (a, b) => {
250    if (a.key === b.key) {
251      return 0;
252    }
253    return a.key < b.key ? -1 : 1;
254  });
255
256  return function(V, opts = kEmptyObject) {
257    const typeV = type(V);
258    switch (typeV) {
259      case 'Undefined':
260      case 'Null':
261      case 'Object':
262        break;
263      default:
264        throw makeException(
265          'can not be converted to a dictionary',
266          opts);
267    }
268    const esDict = V;
269    const idlDict = {};
270
271    // Fast path null and undefined.
272    if (V == null && !hasRequiredKey) {
273      return idlDict;
274    }
275
276    for (const member of new SafeArrayIterator(allMembers)) {
277      const key = member.key;
278
279      let esMemberValue;
280      if (typeV === 'Undefined' || typeV === 'Null') {
281        esMemberValue = undefined;
282      } else {
283        esMemberValue = esDict[key];
284      }
285
286      if (esMemberValue !== undefined) {
287        const context = `'${key}' of '${name}'${
288          opts.context ? ` (${opts.context})` : ''
289        }`;
290        const converter = member.converter;
291        const idlMemberValue = converter(esMemberValue, {
292          __proto__: null,
293          ...opts,
294          context,
295        });
296        setOwnProperty(idlDict, key, idlMemberValue);
297      } else if (member.required) {
298        throw makeException(
299          `can not be converted to '${name}' because '${key}' is required in '${name}'.`,
300          { __proto__: null, ...opts, code: 'ERR_MISSING_OPTION' });
301      }
302    }
303
304    return idlDict;
305  };
306}
307
308function createEnumConverter(name, values) {
309  const E = new SafeSet(values);
310
311  return function(V, opts = kEmptyObject) {
312    const S = String(V);
313
314    if (!E.has(S)) {
315      throw makeException(
316        `value '${S}' is not a valid enum value of type ${name}.`,
317        { __proto__: null, ...opts, code: 'ERR_INVALID_ARG_VALUE' });
318    }
319
320    return S;
321  };
322}
323
324function createSequenceConverter(converter) {
325  return function(V, opts = kEmptyObject) {
326    if (type(V) !== 'Object') {
327      throw makeException(
328        'can not be converted to sequence.',
329        opts);
330    }
331    const iter = V?.[SymbolIterator]?.();
332    if (iter === undefined) {
333      throw makeException(
334        'can not be converted to sequence.',
335        opts);
336    }
337    const array = [];
338    while (true) {
339      const res = iter?.next?.();
340      if (res === undefined) {
341        throw makeException(
342          'can not be converted to sequence.',
343          opts);
344      }
345      if (res.done === true) break;
346      const val = converter(res.value, {
347        __proto__: null,
348        ...opts,
349        context: `${opts.context}, index ${array.length}`,
350      });
351      ArrayPrototypePush(array, val);
352    }
353    return array;
354  };
355}
356
357function createInterfaceConverter(name, prototype) {
358  return (V, opts) => {
359    if (!ObjectPrototypeIsPrototypeOf(prototype, V)) {
360      throw makeException(
361        `is not of type ${name}.`,
362        opts);
363    }
364    return V;
365  };
366}
367
368converters.AlgorithmIdentifier = (V, opts) => {
369  // Union for (object or DOMString)
370  if (type(V) === 'Object') {
371    return converters.object(V, opts);
372  }
373  return converters.DOMString(V, opts);
374};
375
376converters.KeyFormat = createEnumConverter('KeyFormat', [
377  'raw',
378  'pkcs8',
379  'spki',
380  'jwk',
381]);
382
383converters.KeyUsage = createEnumConverter('KeyUsage', [
384  'encrypt',
385  'decrypt',
386  'sign',
387  'verify',
388  'deriveKey',
389  'deriveBits',
390  'wrapKey',
391  'unwrapKey',
392]);
393
394converters['sequence<KeyUsage>'] = createSequenceConverter(converters.KeyUsage);
395
396converters.HashAlgorithmIdentifier = converters.AlgorithmIdentifier;
397
398const dictAlgorithm = [
399  {
400    key: 'name',
401    converter: converters.DOMString,
402    required: true,
403  },
404];
405
406converters.Algorithm = createDictionaryConverter(
407  'Algorithm', dictAlgorithm);
408
409converters.BigInteger = converters.Uint8Array;
410
411const dictRsaKeyGenParams = [
412  ...new SafeArrayIterator(dictAlgorithm),
413  {
414    key: 'modulusLength',
415    converter: (V, opts) =>
416      converters['unsigned long'](V, { ...opts, enforceRange: true }),
417    required: true,
418  },
419  {
420    key: 'publicExponent',
421    converter: converters.BigInteger,
422    required: true,
423  },
424];
425
426converters.RsaKeyGenParams = createDictionaryConverter(
427  'RsaKeyGenParams', dictRsaKeyGenParams);
428
429converters.RsaHashedKeyGenParams = createDictionaryConverter(
430  'RsaHashedKeyGenParams', [
431    ...new SafeArrayIterator(dictRsaKeyGenParams),
432    {
433      key: 'hash',
434      converter: converters.HashAlgorithmIdentifier,
435      required: true,
436    },
437  ]);
438
439converters.RsaHashedImportParams = createDictionaryConverter(
440  'RsaHashedImportParams', [
441    ...new SafeArrayIterator(dictAlgorithm),
442    {
443      key: 'hash',
444      converter: converters.HashAlgorithmIdentifier,
445      required: true,
446    },
447  ]);
448
449converters.NamedCurve = converters.DOMString;
450
451converters.EcKeyImportParams = createDictionaryConverter(
452  'EcKeyImportParams', [
453    ...new SafeArrayIterator(dictAlgorithm),
454    {
455      key: 'namedCurve',
456      converter: converters.NamedCurve,
457      required: true,
458    },
459  ]);
460
461converters.EcKeyGenParams = createDictionaryConverter(
462  'EcKeyGenParams', [
463    ...new SafeArrayIterator(dictAlgorithm),
464    {
465      key: 'namedCurve',
466      converter: converters.NamedCurve,
467      required: true,
468    },
469  ]);
470
471converters.AesKeyGenParams = createDictionaryConverter(
472  'AesKeyGenParams', [
473    ...new SafeArrayIterator(dictAlgorithm),
474    {
475      key: 'length',
476      converter: (V, opts) =>
477        converters['unsigned short'](V, { ...opts, enforceRange: true }),
478      required: true,
479    },
480  ]);
481
482converters.HmacKeyGenParams = createDictionaryConverter(
483  'HmacKeyGenParams', [
484    ...new SafeArrayIterator(dictAlgorithm),
485    {
486      key: 'hash',
487      converter: converters.HashAlgorithmIdentifier,
488      required: true,
489    },
490    {
491      key: 'length',
492      converter: (V, opts) =>
493        converters['unsigned long'](V, { ...opts, enforceRange: true }),
494    },
495  ]);
496
497converters.RsaPssParams = createDictionaryConverter(
498  'RsaPssParams', [
499    ...new SafeArrayIterator(dictAlgorithm),
500    {
501      key: 'saltLength',
502      converter: (V, opts) =>
503        converters['unsigned long'](V, { ...opts, enforceRange: true }),
504      required: true,
505    },
506  ]);
507
508converters.RsaOaepParams = createDictionaryConverter(
509  'RsaOaepParams', [
510    ...new SafeArrayIterator(dictAlgorithm),
511    {
512      key: 'label',
513      converter: converters.BufferSource,
514    },
515  ]);
516
517converters.EcdsaParams = createDictionaryConverter(
518  'EcdsaParams', [
519    ...new SafeArrayIterator(dictAlgorithm),
520    {
521      key: 'hash',
522      converter: converters.HashAlgorithmIdentifier,
523      required: true,
524    },
525  ]);
526
527converters.HmacImportParams = createDictionaryConverter(
528  'HmacImportParams', [
529    ...new SafeArrayIterator(dictAlgorithm),
530    {
531      key: 'hash',
532      converter: converters.HashAlgorithmIdentifier,
533      required: true,
534    },
535    {
536      key: 'length',
537      converter: (V, opts) =>
538        converters['unsigned long'](V, { ...opts, enforceRange: true }),
539    },
540  ]);
541
542const simpleDomStringKey = (key) => ({ key, converter: converters.DOMString });
543
544converters.RsaOtherPrimesInfo = createDictionaryConverter(
545  'RsaOtherPrimesInfo', [
546    simpleDomStringKey('r'),
547    simpleDomStringKey('d'),
548    simpleDomStringKey('t'),
549  ]);
550converters['sequence<RsaOtherPrimesInfo>'] = createSequenceConverter(
551  converters.RsaOtherPrimesInfo);
552
553converters.JsonWebKey = createDictionaryConverter(
554  'JsonWebKey', [
555    simpleDomStringKey('kty'),
556    simpleDomStringKey('use'),
557    {
558      key: 'key_ops',
559      converter: converters['sequence<DOMString>'],
560    },
561    simpleDomStringKey('alg'),
562    {
563      key: 'ext',
564      converter: converters.boolean,
565    },
566    simpleDomStringKey('crv'),
567    simpleDomStringKey('x'),
568    simpleDomStringKey('y'),
569    simpleDomStringKey('d'),
570    simpleDomStringKey('n'),
571    simpleDomStringKey('e'),
572    simpleDomStringKey('p'),
573    simpleDomStringKey('q'),
574    simpleDomStringKey('dp'),
575    simpleDomStringKey('dq'),
576    simpleDomStringKey('qi'),
577    {
578      key: 'oth',
579      converter: converters['sequence<RsaOtherPrimesInfo>'],
580    },
581    simpleDomStringKey('k'),
582  ]);
583
584converters.HkdfParams = createDictionaryConverter(
585  'HkdfParams', [
586    ...new SafeArrayIterator(dictAlgorithm),
587    {
588      key: 'hash',
589      converter: converters.HashAlgorithmIdentifier,
590      required: true,
591    },
592    {
593      key: 'salt',
594      converter: converters.BufferSource,
595      required: true,
596    },
597    {
598      key: 'info',
599      converter: converters.BufferSource,
600      required: true,
601    },
602  ]);
603
604converters.Pbkdf2Params = createDictionaryConverter(
605  'Pbkdf2Params', [
606    ...new SafeArrayIterator(dictAlgorithm),
607    {
608      key: 'hash',
609      converter: converters.HashAlgorithmIdentifier,
610      required: true,
611    },
612    {
613      key: 'iterations',
614      converter: (V, opts) =>
615        converters['unsigned long'](V, { ...opts, enforceRange: true }),
616      required: true,
617    },
618    {
619      key: 'salt',
620      converter: converters.BufferSource,
621      required: true,
622    },
623  ]);
624
625converters.AesDerivedKeyParams = createDictionaryConverter(
626  'AesDerivedKeyParams', [
627    ...new SafeArrayIterator(dictAlgorithm),
628    {
629      key: 'length',
630      converter: (V, opts) =>
631        converters['unsigned short'](V, { ...opts, enforceRange: true }),
632      required: true,
633    },
634  ]);
635
636converters.AesCbcParams = createDictionaryConverter(
637  'AesCbcParams', [
638    ...new SafeArrayIterator(dictAlgorithm),
639    {
640      key: 'iv',
641      converter: converters.BufferSource,
642      required: true,
643    },
644  ]);
645
646converters.AesGcmParams = createDictionaryConverter(
647  'AesGcmParams', [
648    ...new SafeArrayIterator(dictAlgorithm),
649    {
650      key: 'iv',
651      converter: converters.BufferSource,
652      required: true,
653    },
654    {
655      key: 'tagLength',
656      converter: (V, opts) =>
657        converters.octet(V, { ...opts, enforceRange: true }),
658    },
659    {
660      key: 'additionalData',
661      converter: converters.BufferSource,
662    },
663  ]);
664
665converters.AesCtrParams = createDictionaryConverter(
666  'AesCtrParams', [
667    ...new SafeArrayIterator(dictAlgorithm),
668    {
669      key: 'counter',
670      converter: converters.BufferSource,
671      required: true,
672    },
673    {
674      key: 'length',
675      converter: (V, opts) =>
676        converters.octet(V, { ...opts, enforceRange: true }),
677      required: true,
678    },
679  ]);
680
681converters.CryptoKey = createInterfaceConverter(
682  'CryptoKey', CryptoKey.prototype);
683
684converters.EcdhKeyDeriveParams = createDictionaryConverter(
685  'EcdhKeyDeriveParams', [
686    ...new SafeArrayIterator(dictAlgorithm),
687    {
688      key: 'public',
689      converter: converters.CryptoKey,
690      required: true,
691    },
692  ]);
693
694converters.Ed448Params = createDictionaryConverter(
695  'Ed448Params', [
696    ...new SafeArrayIterator(dictAlgorithm),
697    {
698      key: 'context',
699      converter: converters.BufferSource,
700      required: false,
701    },
702  ]);
703
704module.exports = {
705  converters,
706  requiredArguments,
707};
708