Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4e4deca

Browse filesBrowse files
jasnelldanielleadams
authored andcommitted
crypto: implement randomuuid
Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #36729 Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Ben Coe <bencoe@gmail.com>
1 parent a956fb3 commit 4e4deca
Copy full SHA for 4e4deca

File tree

Expand file treeCollapse file tree

6 files changed

+237
-2
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+237
-2
lines changed
Open diff view settings
Collapse file

‎benchmark/crypto/randomUUID.js‎

Copy file name to clipboard
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff 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+
}
Collapse file

‎doc/api/crypto.md‎

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 numberDiff line numberDiff line change
@@ -3160,6 +3160,21 @@ const n = crypto.randomInt(1, 7);
31603160
console.log(`The dice rolled: ${n}`);
31613161
```
31623162

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+
31633178
### `crypto.scrypt(password, salt, keylen[, options], callback)`
31643179
<!-- YAML
31653180
added: v10.5.0
@@ -3929,6 +3944,7 @@ See the [list of SSL OP Flags][] for details.
39293944
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
39303945
[RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt
39313946
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
3947+
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
39323948
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
39333949
[Web Crypto API documentation]: webcrypto.md
39343950
[`Buffer`]: buffer.md
Collapse file

‎lib/crypto.js‎

Copy file name to clipboardExpand all lines: lib/crypto.js
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ const {
5353
randomBytes,
5454
randomFill,
5555
randomFillSync,
56-
randomInt
56+
randomInt,
57+
randomUUID,
5758
} = require('internal/crypto/random');
5859
const {
5960
pbkdf2,
@@ -199,6 +200,7 @@ module.exports = {
199200
randomFill,
200201
randomFillSync,
201202
randomInt,
203+
randomUUID,
202204
scrypt,
203205
scryptSync,
204206
sign: signOneShot,
Collapse file

‎lib/internal/crypto/random.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/random.js
+109-1Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,28 @@ const {
1212
RandomBytesJob,
1313
kCryptoJobAsync,
1414
kCryptoJobSync,
15+
secureBuffer,
1516
} = internalBinding('crypto');
1617

1718
const {
1819
lazyDOMException,
1920
} = require('internal/crypto/util');
2021

21-
const { kMaxLength } = require('buffer');
22+
const { Buffer, kMaxLength } = require('buffer');
2223

2324
const {
2425
codes: {
2526
ERR_INVALID_ARG_TYPE,
2627
ERR_OUT_OF_RANGE,
28+
ERR_OPERATION_FAILED,
2729
}
2830
} = require('internal/errors');
2931

3032
const {
3133
validateNumber,
34+
validateBoolean,
3235
validateCallback,
36+
validateObject,
3337
} = require('internal/validators');
3438

3539
const {
@@ -281,10 +285,114 @@ function getRandomValues(data) {
281285
return data;
282286
}
283287

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+
284391
module.exports = {
285392
randomBytes,
286393
randomFill,
287394
randomFillSync,
288395
randomInt,
289396
getRandomValues,
397+
randomUUID,
290398
};
Collapse file

‎src/crypto/crypto_util.cc‎

Copy file name to clipboardExpand all lines: src/crypto/crypto_util.cc
+34Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ using v8::NewStringType;
3131
using v8::Nothing;
3232
using v8::Object;
3333
using v8::String;
34+
using v8::Uint32;
35+
using v8::Uint8Array;
3436
using v8::Value;
3537

3638
namespace crypto {
@@ -587,6 +589,36 @@ CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args) {
587589
return static_cast<CryptoJobMode>(mode);
588590
}
589591

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+
590622
namespace Util {
591623
void Initialize(Environment* env, Local<Object> target) {
592624
#ifndef OPENSSL_NO_ENGINE
@@ -600,6 +632,8 @@ void Initialize(Environment* env, Local<Object> target) {
600632

601633
NODE_DEFINE_CONSTANT(target, kCryptoJobAsync);
602634
NODE_DEFINE_CONSTANT(target, kCryptoJobSync);
635+
636+
env->SetMethod(target, "secureBuffer", SecureBuffer);
603637
}
604638
} // namespace Util
605639

Collapse file
+58Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff 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+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.