• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayBufferIsView,
5  ArrayBufferPrototypeGetByteLength,
6  ArrayPrototypeIncludes,
7  ArrayPrototypePush,
8  BigInt,
9  DataViewPrototypeGetBuffer,
10  DataViewPrototypeGetByteLength,
11  DataViewPrototypeGetByteOffset,
12  FunctionPrototypeBind,
13  Number,
14  ObjectKeys,
15  ObjectPrototypeHasOwnProperty,
16  Promise,
17  StringPrototypeToUpperCase,
18  Symbol,
19  TypedArrayPrototypeGetBuffer,
20  TypedArrayPrototypeGetByteLength,
21  TypedArrayPrototypeGetByteOffset,
22  TypedArrayPrototypeSlice,
23  Uint8Array,
24} = primordials;
25
26const {
27  getCiphers: _getCiphers,
28  getCurves: _getCurves,
29  getHashes: _getHashes,
30  setEngine: _setEngine,
31  secureHeapUsed: _secureHeapUsed,
32} = internalBinding('crypto');
33
34const { getOptionValue } = require('internal/options');
35
36const {
37  crypto: {
38    ENGINE_METHOD_ALL,
39  },
40} = internalBinding('constants');
41
42const normalizeHashName = require('internal/crypto/hashnames');
43
44const {
45  hideStackFrames,
46  codes: {
47    ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
48    ERR_CRYPTO_ENGINE_UNKNOWN,
49    ERR_INVALID_ARG_TYPE,
50    ERR_INVALID_ARG_VALUE,
51    ERR_OUT_OF_RANGE,
52  },
53} = require('internal/errors');
54
55const {
56  validateArray,
57  validateNumber,
58  validateString,
59} = require('internal/validators');
60
61const { Buffer } = require('buffer');
62
63const {
64  cachedResult,
65  filterDuplicateStrings,
66  lazyDOMException,
67} = require('internal/util');
68
69const {
70  isDataView,
71  isArrayBufferView,
72  isAnyArrayBuffer,
73} = require('internal/util/types');
74
75const kHandle = Symbol('kHandle');
76const kKeyObject = Symbol('kKeyObject');
77
78let defaultEncoding = 'buffer';
79
80function setDefaultEncoding(val) {
81  defaultEncoding = val;
82}
83
84function getDefaultEncoding() {
85  return defaultEncoding;
86}
87
88// This is here because many functions accepted binary strings without
89// any explicit encoding in older versions of node, and we don't want
90// to break them unnecessarily.
91function toBuf(val, encoding) {
92  if (typeof val === 'string') {
93    if (encoding === 'buffer')
94      encoding = 'utf8';
95    return Buffer.from(val, encoding);
96  }
97  return val;
98}
99
100const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers()));
101const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes()));
102const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves()));
103
104function setEngine(id, flags) {
105  validateString(id, 'id');
106  if (flags)
107    validateNumber(flags, 'flags');
108  flags = flags >>> 0;
109
110  // Use provided engine for everything by default
111  if (flags === 0)
112    flags = ENGINE_METHOD_ALL;
113
114  if (typeof _setEngine !== 'function')
115    throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
116  if (!_setEngine(id, flags))
117    throw new ERR_CRYPTO_ENGINE_UNKNOWN(id);
118}
119
120const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => {
121  if (isAnyArrayBuffer(buffer))
122    return buffer;
123  if (typeof buffer === 'string') {
124    if (encoding === 'buffer')
125      encoding = 'utf8';
126    return Buffer.from(buffer, encoding);
127  }
128  if (!isArrayBufferView(buffer)) {
129    throw new ERR_INVALID_ARG_TYPE(
130      name,
131      [
132        'string',
133        'ArrayBuffer',
134        'Buffer',
135        'TypedArray',
136        'DataView',
137      ],
138      buffer,
139    );
140  }
141  return buffer;
142});
143
144// The maximum buffer size that we'll support in the WebCrypto impl
145const kMaxBufferLength = (2 ** 31) - 1;
146
147// The EC named curves that we currently support via the Web Crypto API.
148const kNamedCurveAliases = {
149  'P-256': 'prime256v1',
150  'P-384': 'secp384r1',
151  'P-521': 'secp521r1',
152};
153
154const kAesKeyLengths = [128, 192, 256];
155
156// These are the only hash algorithms we currently support via
157// the Web Crypto API.
158const kHashTypes = [
159  'SHA-1',
160  'SHA-256',
161  'SHA-384',
162  'SHA-512',
163];
164
165const kSupportedAlgorithms = {
166  'digest': {
167    'SHA-1': null,
168    'SHA-256': null,
169    'SHA-384': null,
170    'SHA-512': null,
171  },
172  'generateKey': {
173    'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams',
174    'RSA-PSS': 'RsaHashedKeyGenParams',
175    'RSA-OAEP': 'RsaHashedKeyGenParams',
176    'ECDSA': 'EcKeyGenParams',
177    'ECDH': 'EcKeyGenParams',
178    'AES-CTR': 'AesKeyGenParams',
179    'AES-CBC': 'AesKeyGenParams',
180    'AES-GCM': 'AesKeyGenParams',
181    'AES-KW': 'AesKeyGenParams',
182    'HMAC': 'HmacKeyGenParams',
183    'X25519': null,
184    'Ed25519': null,
185    'X448': null,
186    'Ed448': null,
187  },
188  'sign': {
189    'RSASSA-PKCS1-v1_5': null,
190    'RSA-PSS': 'RsaPssParams',
191    'ECDSA': 'EcdsaParams',
192    'HMAC': null,
193    'Ed25519': null,
194    'Ed448': 'Ed448Params',
195  },
196  'verify': {
197    'RSASSA-PKCS1-v1_5': null,
198    'RSA-PSS': 'RsaPssParams',
199    'ECDSA': 'EcdsaParams',
200    'HMAC': null,
201    'Ed25519': null,
202    'Ed448': 'Ed448Params',
203  },
204  'importKey': {
205    'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams',
206    'RSA-PSS': 'RsaHashedImportParams',
207    'RSA-OAEP': 'RsaHashedImportParams',
208    'ECDSA': 'EcKeyImportParams',
209    'ECDH': 'EcKeyImportParams',
210    'HMAC': 'HmacImportParams',
211    'HKDF': null,
212    'PBKDF2': null,
213    'AES-CTR': null,
214    'AES-CBC': null,
215    'AES-GCM': null,
216    'AES-KW': null,
217    'Ed25519': null,
218    'X25519': null,
219    'Ed448': null,
220    'X448': null,
221  },
222  'deriveBits': {
223    'HKDF': 'HkdfParams',
224    'PBKDF2': 'Pbkdf2Params',
225    'ECDH': 'EcdhKeyDeriveParams',
226    'X25519': 'EcdhKeyDeriveParams',
227    'X448': 'EcdhKeyDeriveParams',
228  },
229  'encrypt': {
230    'RSA-OAEP': 'RsaOaepParams',
231    'AES-CBC': 'AesCbcParams',
232    'AES-GCM': 'AesGcmParams',
233    'AES-CTR': 'AesCtrParams',
234  },
235  'decrypt': {
236    'RSA-OAEP': 'RsaOaepParams',
237    'AES-CBC': 'AesCbcParams',
238    'AES-GCM': 'AesGcmParams',
239    'AES-CTR': 'AesCtrParams',
240  },
241  'get key length': {
242    'AES-CBC': 'AesDerivedKeyParams',
243    'AES-CTR': 'AesDerivedKeyParams',
244    'AES-GCM': 'AesDerivedKeyParams',
245    'AES-KW': 'AesDerivedKeyParams',
246    'HMAC': 'HmacImportParams',
247    'HKDF': null,
248    'PBKDF2': null,
249  },
250  'wrapKey': {
251    'AES-KW': null,
252  },
253  'unwrapKey': {
254    'AES-KW': null,
255  },
256};
257
258const simpleAlgorithmDictionaries = {
259  AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
260  RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
261  EcKeyGenParams: {},
262  HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
263  RsaPssParams: {},
264  EcdsaParams: { hash: 'HashAlgorithmIdentifier' },
265  HmacImportParams: { hash: 'HashAlgorithmIdentifier' },
266  HkdfParams: {
267    hash: 'HashAlgorithmIdentifier',
268    salt: 'BufferSource',
269    info: 'BufferSource',
270  },
271  Ed448Params: { context: 'BufferSource' },
272  Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' },
273  RsaOaepParams: { label: 'BufferSource' },
274  RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' },
275  EcKeyImportParams: {},
276};
277
278function validateMaxBufferLength(data, name) {
279  if (data.byteLength > kMaxBufferLength) {
280    throw lazyDOMException(
281      `${name} must be less than ${kMaxBufferLength + 1} bits`,
282      'OperationError');
283  }
284}
285
286let webidl;
287
288// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
289// adapted for Node.js from Deno's implementation
290// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
291function normalizeAlgorithm(algorithm, op) {
292  if (typeof algorithm === 'string')
293    return normalizeAlgorithm({ name: algorithm }, op);
294
295  webidl ??= require('internal/crypto/webidl');
296
297  // 1.
298  const registeredAlgorithms = kSupportedAlgorithms[op];
299  // 2. 3.
300  const initialAlg = webidl.converters.Algorithm(algorithm, {
301    prefix: 'Failed to normalize algorithm',
302    context: 'passed algorithm',
303  });
304  // 4.
305  let algName = initialAlg.name;
306
307  // 5.
308  let desiredType;
309  for (const key in registeredAlgorithms) {
310    if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) {
311      continue;
312    }
313    if (
314      StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
315    ) {
316      algName = key;
317      desiredType = registeredAlgorithms[key];
318    }
319  }
320  if (desiredType === undefined)
321    throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
322
323  // Fast path everything below if the registered dictionary is null
324  if (desiredType === null)
325    return { name: algName };
326
327  // 6.
328  const normalizedAlgorithm = webidl.converters[desiredType](algorithm, {
329    prefix: 'Failed to normalize algorithm',
330    context: 'passed algorithm',
331  });
332  // 7.
333  normalizedAlgorithm.name = algName;
334
335  // 9.
336  const dict = simpleAlgorithmDictionaries[desiredType];
337  // 10.
338  const dictKeys = dict ? ObjectKeys(dict) : [];
339  for (let i = 0; i < dictKeys.length; i++) {
340    const member = dictKeys[i];
341    if (!ObjectPrototypeHasOwnProperty(dict, member))
342      continue;
343    const idlType = dict[member];
344    const idlValue = normalizedAlgorithm[member];
345    // 3.
346    if (idlType === 'BufferSource' && idlValue) {
347      const isView = ArrayBufferIsView(idlValue);
348      normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
349        new Uint8Array(
350          isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
351          isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
352          isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
353        ),
354      );
355    } else if (idlType === 'HashAlgorithmIdentifier') {
356      normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
357    } else if (idlType === 'AlgorithmIdentifier') {
358      // This extension point is not used by any supported algorithm (yet?)
359      throw lazyDOMException('Not implemented.', 'NotSupportedError');
360    }
361  }
362
363  return normalizedAlgorithm;
364}
365
366function getDataViewOrTypedArrayBuffer(V) {
367  return isDataView(V) ?
368    DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
369}
370
371function getDataViewOrTypedArrayByteOffset(V) {
372  return isDataView(V) ?
373    DataViewPrototypeGetByteOffset(V) : TypedArrayPrototypeGetByteOffset(V);
374}
375
376function getDataViewOrTypedArrayByteLength(V) {
377  return isDataView(V) ?
378    DataViewPrototypeGetByteLength(V) : TypedArrayPrototypeGetByteLength(V);
379}
380
381function hasAnyNotIn(set, checks) {
382  for (const s of set)
383    if (!ArrayPrototypeIncludes(checks, s))
384      return true;
385  return false;
386}
387
388function validateBitLength(length, name, required = false) {
389  if (length !== undefined || required) {
390    validateNumber(length, name);
391    if (length < 0)
392      throw new ERR_OUT_OF_RANGE(name, '> 0');
393    if (length % 8) {
394      throw new ERR_INVALID_ARG_VALUE(
395        name,
396        length,
397        'must be a multiple of 8');
398    }
399  }
400}
401
402function validateByteLength(buf, name, target) {
403  if (buf.byteLength !== target) {
404    throw lazyDOMException(
405      `${name} must contain exactly ${target} bytes`,
406      'OperationError');
407  }
408}
409
410const validateByteSource = hideStackFrames((val, name) => {
411  val = toBuf(val);
412
413  if (isAnyArrayBuffer(val) || isArrayBufferView(val))
414    return val;
415
416  throw new ERR_INVALID_ARG_TYPE(
417    name,
418    [
419      'string',
420      'ArrayBuffer',
421      'TypedArray',
422      'DataView',
423      'Buffer',
424    ],
425    val);
426});
427
428function onDone(resolve, reject, err, result) {
429  if (err) {
430    return reject(lazyDOMException(
431      'The operation failed for an operation-specific reason',
432      { name: 'OperationError', cause: err }));
433  }
434  resolve(result);
435}
436
437function jobPromise(getJob) {
438  return new Promise((resolve, reject) => {
439    try {
440      const job = getJob();
441      job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject);
442      job.run();
443    } catch (err) {
444      onDone(resolve, reject, err);
445    }
446  });
447}
448
449// In WebCrypto, the publicExponent option in RSA is represented as a
450// WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary
451// number of leading zero bits. Our conventional APIs for reading
452// an unsigned int from a Buffer are not adequate. The implementation
453// here is adapted from the chromium implementation here:
454// https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript
455// Returns undefined if the conversion was unsuccessful.
456function bigIntArrayToUnsignedInt(input) {
457  let result = 0;
458
459  for (let n = 0; n < input.length; ++n) {
460    const n_reversed = input.length - n - 1;
461    if (n_reversed >= 4 && input[n])
462      return;  // Too large
463    result |= input[n] << 8 * n_reversed;
464  }
465
466  return result;
467}
468
469function bigIntArrayToUnsignedBigInt(input) {
470  let result = 0n;
471
472  for (let n = 0; n < input.length; ++n) {
473    const n_reversed = input.length - n - 1;
474    result |= BigInt(input[n]) << 8n * BigInt(n_reversed);
475  }
476
477  return result;
478}
479
480function getStringOption(options, key) {
481  let value;
482  if (options && (value = options[key]) != null)
483    validateString(value, `options.${key}`);
484  return value;
485}
486
487function getUsagesUnion(usageSet, ...usages) {
488  const newset = [];
489  for (let n = 0; n < usages.length; n++) {
490    if (usageSet.has(usages[n]))
491      ArrayPrototypePush(newset, usages[n]);
492  }
493  return newset;
494}
495
496function getBlockSize(name) {
497  switch (name) {
498    case 'SHA-1': return 512;
499    case 'SHA-256': return 512;
500    case 'SHA-384': return 1024;
501    case 'SHA-512': return 1024;
502  }
503}
504
505const kKeyOps = {
506  sign: 1,
507  verify: 2,
508  encrypt: 3,
509  decrypt: 4,
510  wrapKey: 5,
511  unwrapKey: 6,
512  deriveKey: 7,
513  deriveBits: 8,
514};
515
516function validateKeyOps(keyOps, usagesSet) {
517  if (keyOps === undefined) return;
518  validateArray(keyOps, 'keyData.key_ops');
519  let flags = 0;
520  for (let n = 0; n < keyOps.length; n++) {
521    const op = keyOps[n];
522    const op_flag = kKeyOps[op];
523    // Skipping unknown key ops
524    if (op_flag === undefined)
525      continue;
526    // Have we seen it already? if so, error
527    if (flags & (1 << op_flag))
528      throw lazyDOMException('Duplicate key operation', 'DataError');
529    flags |= (1 << op_flag);
530
531    // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating
532    // key usage combinations. Specifically, it says that unrelated key
533    // ops SHOULD NOT be used together. We're not yet validating that here.
534  }
535
536  if (usagesSet !== undefined) {
537    for (const use of usagesSet) {
538      if (!ArrayPrototypeIncludes(keyOps, use)) {
539        throw lazyDOMException(
540          'Key operations and usage mismatch',
541          'DataError');
542      }
543    }
544  }
545}
546
547function secureHeapUsed() {
548  const val = _secureHeapUsed();
549  if (val === undefined)
550    return { total: 0, used: 0, utilization: 0, min: 0 };
551  const used = Number(_secureHeapUsed());
552  const total = Number(getOptionValue('--secure-heap'));
553  const min = Number(getOptionValue('--secure-heap-min'));
554  const utilization = used / total;
555  return { total, used, utilization, min };
556}
557
558module.exports = {
559  getArrayBufferOrView,
560  getCiphers,
561  getCurves,
562  getDataViewOrTypedArrayBuffer,
563  getDefaultEncoding,
564  getHashes,
565  kHandle,
566  kKeyObject,
567  setDefaultEncoding,
568  setEngine,
569  toBuf,
570
571  kHashTypes,
572  kNamedCurveAliases,
573  kAesKeyLengths,
574  normalizeAlgorithm,
575  normalizeHashName,
576  hasAnyNotIn,
577  validateBitLength,
578  validateByteLength,
579  validateByteSource,
580  validateKeyOps,
581  jobPromise,
582  validateMaxBufferLength,
583  bigIntArrayToUnsignedBigInt,
584  bigIntArrayToUnsignedInt,
585  getBlockSize,
586  getStringOption,
587  getUsagesUnion,
588  secureHeapUsed,
589};
590