xref: /third_party/node/lib/internal/crypto/random.js (revision 1cb0ef41)
1'use strict';
2
3const {
4  Array,
5  ArrayBufferPrototypeGetByteLength,
6  ArrayPrototypeForEach,
7  ArrayPrototypePush,
8  ArrayPrototypeShift,
9  ArrayPrototypeSplice,
10  BigInt,
11  BigIntPrototypeToString,
12  DataView,
13  DataViewPrototypeGetUint8,
14  FunctionPrototypeBind,
15  FunctionPrototypeCall,
16  MathMin,
17  NumberIsNaN,
18  NumberIsSafeInteger,
19  NumberPrototypeToString,
20  StringFromCharCodeApply,
21  StringPrototypePadStart,
22} = primordials;
23
24const {
25  RandomBytesJob,
26  RandomPrimeJob,
27  CheckPrimeJob,
28  kCryptoJobAsync,
29  kCryptoJobSync,
30  secureBuffer,
31} = internalBinding('crypto');
32
33const {
34  kEmptyObject,
35  lazyDOMException,
36} = require('internal/util');
37
38const { Buffer, kMaxLength } = require('buffer');
39
40const {
41  codes: {
42    ERR_INVALID_ARG_TYPE,
43    ERR_MISSING_ARGS,
44    ERR_OUT_OF_RANGE,
45    ERR_OPERATION_FAILED,
46  },
47} = require('internal/errors');
48
49const {
50  validateNumber,
51  validateBoolean,
52  validateFunction,
53  validateInt32,
54  validateObject,
55} = require('internal/validators');
56
57const {
58  isArrayBufferView,
59  isAnyArrayBuffer,
60  isTypedArray,
61  isFloat32Array,
62  isFloat64Array,
63} = require('internal/util/types');
64
65const { FastBuffer } = require('internal/buffer');
66
67const kMaxInt32 = 2 ** 31 - 1;
68const kMaxPossibleLength = MathMin(kMaxLength, kMaxInt32);
69
70function assertOffset(offset, elementSize, length) {
71  validateNumber(offset, 'offset');
72  offset *= elementSize;
73
74  const maxLength = MathMin(length, kMaxPossibleLength);
75  if (NumberIsNaN(offset) || offset > maxLength || offset < 0) {
76    throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset);
77  }
78
79  return offset >>> 0;  // Convert to uint32.
80}
81
82function assertSize(size, elementSize, offset, length) {
83  validateNumber(size, 'size');
84  size *= elementSize;
85
86  if (NumberIsNaN(size) || size > kMaxPossibleLength || size < 0) {
87    throw new ERR_OUT_OF_RANGE('size',
88                               `>= 0 && <= ${kMaxPossibleLength}`, size);
89  }
90
91  if (size + offset > length) {
92    throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset);
93  }
94
95  return size >>> 0;  // Convert to uint32.
96}
97
98function randomBytes(size, callback) {
99  size = assertSize(size, 1, 0, Infinity);
100  if (callback !== undefined) {
101    validateFunction(callback, 'callback');
102  }
103
104  const buf = new FastBuffer(size);
105
106  if (callback === undefined) {
107    randomFillSync(buf.buffer, 0, size);
108    return buf;
109  }
110
111  // Keep the callback as a regular function so this is propagated.
112  randomFill(buf.buffer, 0, size, function(error) {
113    if (error) return FunctionPrototypeCall(callback, this, error);
114    FunctionPrototypeCall(callback, this, null, buf);
115  });
116}
117
118function randomFillSync(buf, offset = 0, size) {
119  if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
120    throw new ERR_INVALID_ARG_TYPE(
121      'buf',
122      ['ArrayBuffer', 'ArrayBufferView'],
123      buf);
124  }
125
126  const elementSize = buf.BYTES_PER_ELEMENT || 1;
127
128  offset = assertOffset(offset, elementSize, buf.byteLength);
129
130  if (size === undefined) {
131    size = buf.byteLength - offset;
132  } else {
133    size = assertSize(size, elementSize, offset, buf.byteLength);
134  }
135
136  if (size === 0)
137    return buf;
138
139  const job = new RandomBytesJob(
140    kCryptoJobSync,
141    buf,
142    offset,
143    size);
144
145  const err = job.run()[0];
146  if (err)
147    throw err;
148
149  return buf;
150}
151
152function randomFill(buf, offset, size, callback) {
153  if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
154    throw new ERR_INVALID_ARG_TYPE(
155      'buf',
156      ['ArrayBuffer', 'ArrayBufferView'],
157      buf);
158  }
159
160  const elementSize = buf.BYTES_PER_ELEMENT || 1;
161
162  if (typeof offset === 'function') {
163    callback = offset;
164    offset = 0;
165    // Size is a length here, assertSize() call turns it into a number of bytes
166    size = buf.length;
167  } else if (typeof size === 'function') {
168    callback = size;
169    size = buf.length - offset;
170  } else {
171    validateFunction(callback, 'callback');
172  }
173
174  offset = assertOffset(offset, elementSize, buf.byteLength);
175
176  if (size === undefined) {
177    size = buf.byteLength - offset;
178  } else {
179    size = assertSize(size, elementSize, offset, buf.byteLength);
180  }
181
182  if (size === 0) {
183    callback(null, buf);
184    return;
185  }
186
187  const job = new RandomBytesJob(
188    kCryptoJobAsync,
189    buf,
190    offset,
191    size);
192  job.ondone = FunctionPrototypeBind(onJobDone, job, buf, callback);
193  job.run();
194}
195
196// Largest integer we can read from a buffer.
197// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6);
198const RAND_MAX = 0xFFFF_FFFF_FFFF;
199
200// Cache random data to use in randomInt. The cache size must be evenly
201// divisible by 6 because each attempt to obtain a random int uses 6 bytes.
202const randomCache = new FastBuffer(6 * 1024);
203let randomCacheOffset = randomCache.length;
204let asyncCacheFillInProgress = false;
205const asyncCachePendingTasks = [];
206
207// Generates an integer in [min, max) range where min is inclusive and max is
208// exclusive.
209function randomInt(min, max, callback) {
210  // Detect optional min syntax
211  // randomInt(max)
212  // randomInt(max, callback)
213  const minNotSpecified = typeof max === 'undefined' ||
214    typeof max === 'function';
215
216  if (minNotSpecified) {
217    callback = max;
218    max = min;
219    min = 0;
220  }
221
222  const isSync = typeof callback === 'undefined';
223  if (!isSync) {
224    validateFunction(callback, 'callback');
225  }
226  if (!NumberIsSafeInteger(min)) {
227    throw new ERR_INVALID_ARG_TYPE('min', 'a safe integer', min);
228  }
229  if (!NumberIsSafeInteger(max)) {
230    throw new ERR_INVALID_ARG_TYPE('max', 'a safe integer', max);
231  }
232  if (max <= min) {
233    throw new ERR_OUT_OF_RANGE(
234      'max', `greater than the value of "min" (${min})`, max,
235    );
236  }
237
238  // First we generate a random int between [0..range)
239  const range = max - min;
240
241  if (!(range <= RAND_MAX)) {
242    throw new ERR_OUT_OF_RANGE(`max${minNotSpecified ? '' : ' - min'}`,
243                               `<= ${RAND_MAX}`, range);
244  }
245
246  // For (x % range) to produce an unbiased value greater than or equal to 0 and
247  // less than range, x must be drawn randomly from the set of integers greater
248  // than or equal to 0 and less than randLimit.
249  const randLimit = RAND_MAX - (RAND_MAX % range);
250
251  // If we don't have a callback, or if there is still data in the cache, we can
252  // do this synchronously, which is super fast.
253  while (isSync || (randomCacheOffset < randomCache.length)) {
254    if (randomCacheOffset === randomCache.length) {
255      // This might block the thread for a bit, but we are in sync mode.
256      randomFillSync(randomCache);
257      randomCacheOffset = 0;
258    }
259
260    const x = randomCache.readUIntBE(randomCacheOffset, 6);
261    randomCacheOffset += 6;
262
263    if (x < randLimit) {
264      const n = (x % range) + min;
265      if (isSync) return n;
266      process.nextTick(callback, undefined, n);
267      return;
268    }
269  }
270
271  // At this point, we are in async mode with no data in the cache. We cannot
272  // simply refill the cache, because another async call to randomInt might
273  // already be doing that. Instead, queue this call for when the cache has
274  // been refilled.
275  ArrayPrototypePush(asyncCachePendingTasks, { min, max, callback });
276  asyncRefillRandomIntCache();
277}
278
279function asyncRefillRandomIntCache() {
280  if (asyncCacheFillInProgress)
281    return;
282
283  asyncCacheFillInProgress = true;
284  randomFill(randomCache, (err) => {
285    asyncCacheFillInProgress = false;
286
287    const tasks = asyncCachePendingTasks;
288    const errorReceiver = err && ArrayPrototypeShift(tasks);
289    if (!err)
290      randomCacheOffset = 0;
291
292    // Restart all pending tasks. If an error occurred, we only notify a single
293    // callback (errorReceiver) about it. This way, every async call to
294    // randomInt has a chance of being successful, and it avoids complex
295    // exception handling here.
296    ArrayPrototypeForEach(ArrayPrototypeSplice(tasks, 0), (task) => {
297      randomInt(task.min, task.max, task.callback);
298    });
299
300    // This is the only call that might throw, and is therefore done at the end.
301    if (errorReceiver)
302      errorReceiver.callback(err);
303  });
304}
305
306
307function onJobDone(buf, callback, error) {
308  if (error) return FunctionPrototypeCall(callback, this, error);
309  FunctionPrototypeCall(callback, this, null, buf);
310}
311
312// Really just the Web Crypto API alternative
313// to require('crypto').randomFillSync() with an
314// additional limitation that the input buffer is
315// not allowed to exceed 65536 bytes, and can only
316// be an integer-type TypedArray.
317function getRandomValues(data) {
318  if (arguments.length < 1)
319    throw new ERR_MISSING_ARGS('typedArray');
320  if (!isTypedArray(data) ||
321      isFloat32Array(data) ||
322      isFloat64Array(data)) {
323    // Ordinarily this would be an ERR_INVALID_ARG_TYPE. However,
324    // the Web Crypto API and web platform tests expect this to
325    // be a DOMException with type TypeMismatchError.
326    throw lazyDOMException(
327      'The data argument must be an integer-type TypedArray',
328      'TypeMismatchError');
329  }
330  if (data.byteLength > 65536) {
331    throw lazyDOMException(
332      'The requested length exceeds 65,536 bytes',
333      'QuotaExceededError');
334  }
335  randomFillSync(data, 0);
336  return data;
337}
338
339// Implements an RFC 4122 version 4 random UUID.
340// To improve performance, random data is generated in batches
341// large enough to cover kBatchSize UUID's at a time. The uuidData
342// buffer is reused. Each call to randomUUID() consumes 16 bytes
343// from the buffer.
344
345const kBatchSize = 128;
346let uuidData;
347let uuidNotBuffered;
348let uuidBatch = 0;
349
350let hexBytesCache;
351function getHexBytes() {
352  if (hexBytesCache === undefined) {
353    hexBytesCache = new Array(256);
354    for (let i = 0; i < hexBytesCache.length; i++) {
355      const hex = NumberPrototypeToString(i, 16);
356      hexBytesCache[i] = StringPrototypePadStart(hex, 2, '0');
357    }
358  }
359  return hexBytesCache;
360}
361
362function serializeUUID(buf, offset = 0) {
363  const kHexBytes = getHexBytes();
364  // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
365  return kHexBytes[buf[offset]] +
366    kHexBytes[buf[offset + 1]] +
367    kHexBytes[buf[offset + 2]] +
368    kHexBytes[buf[offset + 3]] +
369    '-' +
370    kHexBytes[buf[offset + 4]] +
371    kHexBytes[buf[offset + 5]] +
372    '-' +
373    kHexBytes[(buf[offset + 6] & 0x0f) | 0x40] +
374    kHexBytes[buf[offset + 7]] +
375    '-' +
376    kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] +
377    kHexBytes[buf[offset + 9]] +
378    '-' +
379    kHexBytes[buf[offset + 10]] +
380    kHexBytes[buf[offset + 11]] +
381    kHexBytes[buf[offset + 12]] +
382    kHexBytes[buf[offset + 13]] +
383    kHexBytes[buf[offset + 14]] +
384    kHexBytes[buf[offset + 15]];
385}
386
387function getBufferedUUID() {
388  uuidData ??= secureBuffer(16 * kBatchSize);
389  if (uuidData === undefined)
390    throw new ERR_OPERATION_FAILED('Out of memory');
391
392  if (uuidBatch === 0) randomFillSync(uuidData);
393  uuidBatch = (uuidBatch + 1) % kBatchSize;
394  return serializeUUID(uuidData, uuidBatch * 16);
395}
396
397function getUnbufferedUUID() {
398  uuidNotBuffered ??= secureBuffer(16);
399  if (uuidNotBuffered === undefined)
400    throw new ERR_OPERATION_FAILED('Out of memory');
401  randomFillSync(uuidNotBuffered);
402  return serializeUUID(uuidNotBuffered);
403}
404
405function randomUUID(options) {
406  if (options !== undefined)
407    validateObject(options, 'options');
408  const {
409    disableEntropyCache = false,
410  } = options || kEmptyObject;
411
412  validateBoolean(disableEntropyCache, 'options.disableEntropyCache');
413
414  return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID();
415}
416
417function createRandomPrimeJob(type, size, options) {
418  validateObject(options, 'options');
419
420  const {
421    safe = false,
422    bigint = false,
423  } = options;
424  let {
425    add,
426    rem,
427  } = options;
428
429  validateBoolean(safe, 'options.safe');
430  validateBoolean(bigint, 'options.bigint');
431
432  if (add !== undefined) {
433    if (typeof add === 'bigint') {
434      add = unsignedBigIntToBuffer(add, 'options.add');
435    } else if (!isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
436      throw new ERR_INVALID_ARG_TYPE(
437        'options.add',
438        [
439          'ArrayBuffer',
440          'TypedArray',
441          'Buffer',
442          'DataView',
443          'bigint',
444        ],
445        add);
446    }
447  }
448
449  if (rem !== undefined) {
450    if (typeof rem === 'bigint') {
451      rem = unsignedBigIntToBuffer(rem, 'options.rem');
452    } else if (!isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
453      throw new ERR_INVALID_ARG_TYPE(
454        'options.rem',
455        [
456          'ArrayBuffer',
457          'TypedArray',
458          'Buffer',
459          'DataView',
460          'bigint',
461        ],
462        rem);
463    }
464  }
465
466  const job = new RandomPrimeJob(type, size, safe, add, rem);
467  job.result = bigint ? arrayBufferToUnsignedBigInt : (p) => p;
468  return job;
469}
470
471function generatePrime(size, options, callback) {
472  validateInt32(size, 'size', 1);
473  if (typeof options === 'function') {
474    callback = options;
475    options = kEmptyObject;
476  }
477  validateFunction(callback, 'callback');
478
479  const job = createRandomPrimeJob(kCryptoJobAsync, size, options);
480  job.ondone = (err, prime) => {
481    if (err) {
482      callback(err);
483      return;
484    }
485
486    callback(
487      undefined,
488      job.result(prime));
489  };
490  job.run();
491}
492
493function generatePrimeSync(size, options = kEmptyObject) {
494  validateInt32(size, 'size', 1);
495
496  const job = createRandomPrimeJob(kCryptoJobSync, size, options);
497  const { 0: err, 1: prime } = job.run();
498  if (err)
499    throw err;
500  return job.result(prime);
501}
502
503/**
504 * 48 is the ASCII code for '0', 97 is the ASCII code for 'a'.
505 * @param {number} number An integer between 0 and 15.
506 * @returns {number} corresponding to the ASCII code of the hex representation
507 *                   of the parameter.
508 */
509const numberToHexCharCode = (number) => (number < 10 ? 48 : 87) + number;
510
511/**
512 * @param {ArrayBuffer} buf An ArrayBuffer.
513 * @return {bigint}
514 */
515function arrayBufferToUnsignedBigInt(buf) {
516  const length = ArrayBufferPrototypeGetByteLength(buf);
517  const chars = Array(length * 2);
518  const view = new DataView(buf);
519
520  for (let i = 0; i < length; i++) {
521    const val = DataViewPrototypeGetUint8(view, i);
522    chars[2 * i] = numberToHexCharCode(val >> 4);
523    chars[2 * i + 1] = numberToHexCharCode(val & 0xf);
524  }
525
526  return BigInt(`0x${StringFromCharCodeApply(chars)}`);
527}
528
529function unsignedBigIntToBuffer(bigint, name) {
530  if (bigint < 0) {
531    throw new ERR_OUT_OF_RANGE(name, '>= 0', bigint);
532  }
533
534  const hex = BigIntPrototypeToString(bigint, 16);
535  const padded = StringPrototypePadStart(hex, hex.length + (hex.length % 2), 0);
536  return Buffer.from(padded, 'hex');
537}
538
539function checkPrime(candidate, options = kEmptyObject, callback) {
540  if (typeof candidate === 'bigint')
541    candidate = unsignedBigIntToBuffer(candidate, 'candidate');
542  if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
543    throw new ERR_INVALID_ARG_TYPE(
544      'candidate',
545      [
546        'ArrayBuffer',
547        'TypedArray',
548        'Buffer',
549        'DataView',
550        'bigint',
551      ],
552      candidate,
553    );
554  }
555  if (typeof options === 'function') {
556    callback = options;
557    options = kEmptyObject;
558  }
559  validateFunction(callback, 'callback');
560  validateObject(options, 'options');
561  const {
562    checks = 0,
563  } = options;
564
565  // The checks option is unsigned but must fit into a signed C int for OpenSSL.
566  validateInt32(checks, 'options.checks', 0);
567
568  const job = new CheckPrimeJob(kCryptoJobAsync, candidate, checks);
569  job.ondone = callback;
570  job.run();
571}
572
573function checkPrimeSync(candidate, options = kEmptyObject) {
574  if (typeof candidate === 'bigint')
575    candidate = unsignedBigIntToBuffer(candidate, 'candidate');
576  if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
577    throw new ERR_INVALID_ARG_TYPE(
578      'candidate',
579      [
580        'ArrayBuffer',
581        'TypedArray',
582        'Buffer',
583        'DataView',
584        'bigint',
585      ],
586      candidate,
587    );
588  }
589  validateObject(options, 'options');
590  const {
591    checks = 0,
592  } = options;
593
594  // The checks option is unsigned but must fit into a signed C int for OpenSSL.
595  validateInt32(checks, 'options.checks', 0);
596
597  const job = new CheckPrimeJob(kCryptoJobSync, candidate, checks);
598  const { 0: err, 1: result } = job.run();
599  if (err)
600    throw err;
601
602  return result;
603}
604
605module.exports = {
606  checkPrime,
607  checkPrimeSync,
608  randomBytes,
609  randomFill,
610  randomFillSync,
611  randomInt,
612  getRandomValues,
613  randomUUID,
614  generatePrime,
615  generatePrimeSync,
616};
617