GeezerVison is showing github.com — crypto: implement randomuuid · nodejs/node@4e4deca
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +17Lines changed: 17 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboardExpand all lines: doc/api/crypto.md +16Lines changed: 16 additions & 0 deletions - Display the source diff
- Display the rich diff
| 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 | |
Collapse file Copy file name to clipboardExpand all lines: lib/crypto.js +3-1Lines changed: 3 additions & 1 deletion | 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, | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/random.js +109-1Lines changed: 109 additions & 1 deletion | 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 | }; |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_util.cc +34Lines changed: 34 additions & 0 deletions | 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 |
| |
Collapse file Copy file name to clipboard +58Lines changed: 58 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
|