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