| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a956fb3 commit 4e4deca
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,17 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common.js'); | ||
| 4 | + const { randomUUID } = require('crypto'); | ||
| 5 | + | ||
| 6 | + const bench = common.createBenchmark(main, { | ||
| 7 | + n: [1e7], | ||
| 8 | + disableEntropyCache: [0, 1], | ||
| 9 | + }); | ||
| 10 | + | ||
| 11 | + function main({ n, disableEntropyCache }) { | ||
| 12 | + disableEntropyCache = !!disableEntropyCache; | ||
| 13 | + bench.start(); | ||
| 14 | + for (let i = 0; i < n; ++i) | ||
| 15 | + randomUUID({ disableEntropyCache }); | ||
| 16 | + bench.end(n); | ||
| 17 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3160,6 +3160,21 @@ const n = crypto.randomInt(1, 7); | |||
| 3160 | 3160 | console.log(`The dice rolled: ${n}`); | |
| 3161 | 3161 | ``` | |
| 3162 | 3162 | ||
| 3163 | + ### `crypto.randomUUID([options])` | ||
| 3164 | + <!-- YAML | ||
| 3165 | + added: REPLACEME | ||
| 3166 | + --> | ||
| 3167 | + | ||
| 3168 | + * `options` {Object} | ||
| 3169 | + * `disableEntropyCache` {boolean} By default, to improve performance, | ||
| 3170 | + Node.js will pre-emptively generate and persistently cache enough | ||
| 3171 | + random data to generate up to 128 random UUIDs. To generate a UUID | ||
| 3172 | + without using the cache, set `disableEntropyCache` to `true`. | ||
| 3173 | + **Defaults**: `false`. | ||
| 3174 | + * Returns: {string} | ||
| 3175 | + | ||
| 3176 | + Generates a random [RFC 4122][] Version 4 UUID. | ||
| 3177 | + | ||
| 3163 | 3178 | ### `crypto.scrypt(password, salt, keylen[, options], callback)` | |
| 3164 | 3179 | <!-- YAML | |
| 3165 | 3180 | added: v10.5.0 | |
@@ -3929,6 +3944,7 @@ See the [list of SSL OP Flags][] for details. | |||
| 3929 | 3944 | [RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt | |
| 3930 | 3945 | [RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt | |
| 3931 | 3946 | [RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt | |
| 3947 | + [RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt | ||
| 3932 | 3948 | [RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt | |
| 3933 | 3949 | [Web Crypto API documentation]: webcrypto.md | |
| 3934 | 3950 | [`Buffer`]: buffer.md | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -53,7 +53,8 @@ const { | |||
| 53 | 53 | randomBytes, | |
| 54 | 54 | randomFill, | |
| 55 | 55 | randomFillSync, | |
| 56 | - randomInt | ||
| 56 | + randomInt, | ||
| 57 | + randomUUID, | ||
| 57 | 58 | } = require('internal/crypto/random'); | |
| 58 | 59 | const { | |
| 59 | 60 | pbkdf2, | |
@@ -199,6 +200,7 @@ module.exports = { | |||
| 199 | 200 | randomFill, | |
| 200 | 201 | randomFillSync, | |
| 201 | 202 | randomInt, | |
| 203 | + randomUUID, | ||
| 202 | 204 | scrypt, | |
| 203 | 205 | scryptSync, | |
| 204 | 206 | sign: signOneShot, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,24 +12,28 @@ const { | |||
| 12 | 12 | RandomBytesJob, | |
| 13 | 13 | kCryptoJobAsync, | |
| 14 | 14 | kCryptoJobSync, | |
| 15 | + secureBuffer, | ||
| 15 | 16 | } = internalBinding('crypto'); | |
| 16 | 17 | ||
| 17 | 18 | const { | |
| 18 | 19 | lazyDOMException, | |
| 19 | 20 | } = require('internal/crypto/util'); | |
| 20 | 21 | ||
| 21 | - const { kMaxLength } = require('buffer'); | ||
| 22 | + const { Buffer, kMaxLength } = require('buffer'); | ||
| 22 | 23 | ||
| 23 | 24 | const { | |
| 24 | 25 | codes: { | |
| 25 | 26 | ERR_INVALID_ARG_TYPE, | |
| 26 | 27 | ERR_OUT_OF_RANGE, | |
| 28 | + ERR_OPERATION_FAILED, | ||
| 27 | 29 | } | |
| 28 | 30 | } = require('internal/errors'); | |
| 29 | 31 | ||
| 30 | 32 | const { | |
| 31 | 33 | validateNumber, | |
| 34 | + validateBoolean, | ||
| 32 | 35 | validateCallback, | |
| 36 | + validateObject, | ||
| 33 | 37 | } = require('internal/validators'); | |
| 34 | 38 | ||
| 35 | 39 | const { | |
@@ -281,10 +285,114 @@ function getRandomValues(data) { | |||
| 281 | 285 | return data; | |
| 282 | 286 | } | |
| 283 | 287 | ||
| 288 | + // Implements an RFC 4122 version 4 random UUID. | ||
| 289 | + // To improve performance, random data is generated in batches | ||
| 290 | + // large enough to cover kBatchSize UUID's at a time. The uuidData | ||
| 291 | + // and uuid buffers are reused. Each call to randomUUID() consumes | ||
| 292 | + // 16 bytes from the buffer. | ||
| 293 | + | ||
| 294 | + const kHexDigits = [ | ||
| 295 | + 48, 49, 50, 51, 52, 53, 54, 55, | ||
| 296 | + 56, 57, 97, 98, 99, 100, 101, 102 | ||
| 297 | + ]; | ||
| 298 | + | ||
| 299 | + const kBatchSize = 128; | ||
| 300 | + let uuidData; | ||
| 301 | + let uuidNotBuffered; | ||
| 302 | + let uuid; | ||
| 303 | + let uuidBatch = 0; | ||
| 304 | + | ||
| 305 | + function getBufferedUUID() { | ||
| 306 | + if (uuidData === undefined) { | ||
| 307 | + uuidData = secureBuffer(16 * kBatchSize); | ||
| 308 | + if (uuidData === undefined) | ||
| 309 | + throw new ERR_OPERATION_FAILED('Out of memory'); | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + if (uuidBatch === 0) randomFillSync(uuidData); | ||
| 313 | + uuidBatch = (uuidBatch + 1) % kBatchSize; | ||
| 314 | + return uuidData.slice(uuidBatch * 16, (uuidBatch * 16) + 16); | ||
| 315 | + } | ||
| 316 | + | ||
| 317 | + function randomUUID(options) { | ||
| 318 | + if (options !== undefined) | ||
| 319 | + validateObject(options, 'options'); | ||
| 320 | + const { | ||
| 321 | + disableEntropyCache = false, | ||
| 322 | + } = { ...options }; | ||
| 323 | + | ||
| 324 | + validateBoolean(disableEntropyCache, 'options.disableEntropyCache'); | ||
| 325 | + | ||
| 326 | + if (uuid === undefined) { | ||
| 327 | + uuid = Buffer.alloc(36, '-'); | ||
| 328 | + uuid[14] = 52; // '4', identifies the UUID version | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + let uuidBuf; | ||
| 332 | + if (!disableEntropyCache) { | ||
| 333 | + uuidBuf = getBufferedUUID(); | ||
| 334 | + } else { | ||
| 335 | + uuidBuf = uuidNotBuffered; | ||
| 336 | + if (uuidBuf === undefined) | ||
| 337 | + uuidBuf = uuidNotBuffered = secureBuffer(16); | ||
| 338 | + if (uuidBuf === undefined) | ||
| 339 | + throw new ERR_OPERATION_FAILED('Out of memory'); | ||
| 340 | + randomFillSync(uuidBuf); | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + // Variant byte: 10xxxxxx (variant 1) | ||
| 344 | + uuidBuf[8] = (uuidBuf[8] & 0x3f) | 0x80; | ||
| 345 | + | ||
| 346 | + // This function is structured the way it is for performance. | ||
| 347 | + // The uuid buffer stores the serialization of the random | ||
| 348 | + // bytes from uuidData. | ||
| 349 | + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ||
| 350 | + let n = 0; | ||
| 351 | + uuid[0] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 352 | + uuid[1] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 353 | + uuid[2] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 354 | + uuid[3] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 355 | + uuid[4] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 356 | + uuid[5] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 357 | + uuid[6] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 358 | + uuid[7] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 359 | + // - | ||
| 360 | + uuid[9] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 361 | + uuid[10] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 362 | + uuid[11] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 363 | + uuid[12] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 364 | + // - | ||
| 365 | + // 4, uuid[14] is set already... | ||
| 366 | + uuid[15] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 367 | + uuid[16] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 368 | + uuid[17] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 369 | + // - | ||
| 370 | + uuid[19] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 371 | + uuid[20] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 372 | + uuid[21] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 373 | + uuid[22] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 374 | + // - | ||
| 375 | + uuid[24] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 376 | + uuid[25] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 377 | + uuid[26] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 378 | + uuid[27] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 379 | + uuid[28] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 380 | + uuid[29] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 381 | + uuid[30] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 382 | + uuid[31] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 383 | + uuid[32] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 384 | + uuid[33] = kHexDigits[uuidBuf[n++] & 0xf]; | ||
| 385 | + uuid[34] = kHexDigits[uuidBuf[n] >> 4]; | ||
| 386 | + uuid[35] = kHexDigits[uuidBuf[n] & 0xf]; | ||
| 387 | + | ||
| 388 | + return uuid.latin1Slice(0, 36); | ||
| 389 | + } | ||
| 390 | + | ||
| 284 | 391 | module.exports = { | |
| 285 | 392 | randomBytes, | |
| 286 | 393 | randomFill, | |
| 287 | 394 | randomFillSync, | |
| 288 | 395 | randomInt, | |
| 289 | 396 | getRandomValues, | |
| 397 | + randomUUID, | ||
| 290 | 398 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -31,6 +31,8 @@ using v8::NewStringType; | |||
| 31 | 31 | using v8::Nothing; | |
| 32 | 32 | using v8::Object; | |
| 33 | 33 | using v8::String; | |
| 34 | + using v8::Uint32; | ||
| 35 | + using v8::Uint8Array; | ||
| 34 | 36 | using v8::Value; | |
| 35 | 37 | ||
| 36 | 38 | namespace crypto { | |
@@ -587,6 +589,36 @@ CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args) { | |||
| 587 | 589 | return static_cast<CryptoJobMode>(mode); | |
| 588 | 590 | } | |
| 589 | 591 | ||
| 592 | + namespace { | ||
| 593 | + // SecureBuffer uses openssl to allocate a Uint8Array using | ||
| 594 | + // OPENSSL_secure_malloc. Because we do not yet actually | ||
| 595 | + // make use of secure heap, this has the same semantics as | ||
| 596 | + // using OPENSSL_malloc. However, if the secure heap is | ||
| 597 | + // initialized, SecureBuffer will automatically use it. | ||
| 598 | + void SecureBuffer(const FunctionCallbackInfo<Value>& args) { | ||
| 599 | + CHECK(args[0]->IsUint32()); | ||
| 600 | + Environment* env = Environment::GetCurrent(args); | ||
| 601 | + uint32_t len = args[0].As<Uint32>()->Value(); | ||
| 602 | + char* data = static_cast<char*>(OPENSSL_secure_malloc(len)); | ||
| 603 | + if (data == nullptr) { | ||
| 604 | + // There's no memory available for the allocation. | ||
| 605 | + // Return nothing. | ||
| 606 | + return; | ||
| 607 | + } | ||
| 608 | + memset(data, 0, len); | ||
| 609 | + std::shared_ptr<BackingStore> store = | ||
| 610 | + ArrayBuffer::NewBackingStore( | ||
| 611 | + data, | ||
| 612 | + len, | ||
| 613 | + [](void* data, size_t len, void* deleter_data) { | ||
| 614 | + OPENSSL_secure_clear_free(data, len); | ||
| 615 | + }, | ||
| 616 | + data); | ||
| 617 | + Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store); | ||
| 618 | + args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len)); | ||
| 619 | + } | ||
| 620 | + } // namespace | ||
| 621 | + | ||
| 590 | 622 | namespace Util { | |
| 591 | 623 | void Initialize(Environment* env, Local<Object> target) { | |
| 592 | 624 | #ifndef OPENSSL_NO_ENGINE | |
@@ -600,6 +632,8 @@ void Initialize(Environment* env, Local<Object> target) { | |||
| 600 | 632 | ||
| 601 | 633 | NODE_DEFINE_CONSTANT(target, kCryptoJobAsync); | |
| 602 | 634 | NODE_DEFINE_CONSTANT(target, kCryptoJobSync); | |
| 635 | + | ||
| 636 | + env->SetMethod(target, "secureBuffer", SecureBuffer); | ||
| 603 | 637 | } | |
| 604 | 638 | } // namespace Util | |
| 605 | 639 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,58 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + | ||
| 5 | + if (!common.hasCrypto) | ||
| 6 | + common.skip('missing crypto'); | ||
| 7 | + | ||
| 8 | + const assert = require('assert'); | ||
| 9 | + const { | ||
| 10 | + randomUUID, | ||
| 11 | + } = require('crypto'); | ||
| 12 | + | ||
| 13 | + const last = new Set([ | ||
| 14 | + '00000000-0000-0000-0000-000000000000' | ||
| 15 | + ]); | ||
| 16 | + | ||
| 17 | + function testMatch(uuid) { | ||
| 18 | + assert.match( | ||
| 19 | + uuid, | ||
| 20 | + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + // Generate a number of UUID's to make sure we're | ||
| 24 | + // not just generating the same value over and over | ||
| 25 | + // and to make sure the batching changes the random | ||
| 26 | + // bytes. | ||
| 27 | + for (let n = 0; n < 130; n++) { | ||
| 28 | + const uuid = randomUUID(); | ||
| 29 | + assert(!last.has(uuid)); | ||
| 30 | + last.add(uuid); | ||
| 31 | + assert.strictEqual(typeof uuid, 'string'); | ||
| 32 | + assert.strictEqual(uuid.length, 36); | ||
| 33 | + testMatch(uuid); | ||
| 34 | + | ||
| 35 | + // Check that version 4 identifier was populated. | ||
| 36 | + assert.strictEqual( | ||
| 37 | + Buffer.from(uuid.substr(14, 2), 'hex')[0] & 0x40, 0x40); | ||
| 38 | + | ||
| 39 | + // Check that clock_seq_hi_and_reserved was populated with reserved bits. | ||
| 40 | + assert.strictEqual( | ||
| 41 | + Buffer.from(uuid.substr(19, 2), 'hex')[0] & 0b1100_0000, 0b1000_0000); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + // Test non-buffered UUID's | ||
| 45 | + { | ||
| 46 | + testMatch(randomUUID({ disableEntropyCache: true })); | ||
| 47 | + testMatch(randomUUID({ disableEntropyCache: true })); | ||
| 48 | + testMatch(randomUUID({ disableEntropyCache: true })); | ||
| 49 | + testMatch(randomUUID({ disableEntropyCache: true })); | ||
| 50 | + | ||
| 51 | + assert.throws(() => randomUUID(1), { | ||
| 52 | + code: 'ERR_INVALID_ARG_TYPE' | ||
| 53 | + }); | ||
| 54 | + | ||
| 55 | + assert.throws(() => randomUUID({ disableEntropyCache: '' }), { | ||
| 56 | + code: 'ERR_INVALID_ARG_TYPE' | ||
| 57 | + }); | ||
| 58 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments