Skip to content

Commit e983a0d

Browse files
committed
feat: remove insecure fallback random number generator
BREAKING CHANGE: Remove builtin support for insecure random number generators in the browser. Users who want that will have to supply their own random number generator function. Fixes #173.
1 parent d6c7378 commit e983a0d

9 files changed

+100
-53
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Features:
1111
- Support for version 1, 3, 4 and 5 UUIDs
1212
- Cross-platform: CommonJS build for Node.js and [ECMAScript Modules](#ecmascript-modules) for the
1313
browser.
14-
- Uses cryptographically-strong random number APIs (when available)
14+
- Uses cryptographically-strong random number APIs
1515
- Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883))
1616

1717
## Quickstart - Node.js/CommonJS
@@ -128,12 +128,12 @@ uuid.v1(options, buffer, offset);
128128
Generate and return a RFC4122 v1 (timestamp-based) UUID.
129129

130130
- `options` - (Object) Optional uuid state to apply. Properties may include:
131-
132131
- `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1.
133132
- `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used.
134133
- `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used.
135134
- `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2.
136-
135+
- `random` - (Number[16]) Array of 16 numbers (0-255) to use for initialization of `node` and `clockseq` as described above. Takes precedence over `options.rng`.
136+
- `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`.
137137
- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
138138
- `offset` - (Number) Starting index in `buffer` at which to begin writing.
139139

README_js.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Features:
2424
- Support for version 1, 3, 4 and 5 UUIDs
2525
- Cross-platform: CommonJS build for Node.js and [ECMAScript Modules](#ecmascript-modules) for the
2626
browser.
27-
- Uses cryptographically-strong random number APIs (when available)
27+
- Uses cryptographically-strong random number APIs
2828
- Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883))
2929

3030
## Quickstart - Node.js/CommonJS
@@ -137,12 +137,12 @@ uuid.v1(options, buffer, offset);
137137
Generate and return a RFC4122 v1 (timestamp-based) UUID.
138138

139139
- `options` - (Object) Optional uuid state to apply. Properties may include:
140-
141140
- `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1.
142141
- `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used.
143142
- `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used.
144143
- `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2.
145-
144+
- `random` - (Number[16]) Array of 16 numbers (0-255) to use for initialization of `node` and `clockseq` as described above. Takes precedence over `options.rng`.
145+
- `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`.
146146
- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
147147
- `offset` - (Number) Starting index in `buffer` at which to begin writing.
148148

src/rng-browser.js

+13-33
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,21 @@
1-
// Unique ID creation requires a high quality random # generator. In the
2-
// browser this is a little complicated due to unknown quality of Math.random()
3-
// and inconsistent support for the `crypto` API. We do the best we can via
4-
// feature-detection
1+
// Unique ID creation requires a high quality random # generator. In the browser we therefore
2+
// require the crypto API and do not support built-in fallback to lower quality random number
3+
// generators (like Math.random()).
54

6-
// getRandomValues needs to be invoked in a context where "this" is a Crypto
7-
// implementation. Also, find the complete implementation of crypto on IE11.
5+
// getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. Also,
6+
// find the complete implementation of crypto (msCrypto) on IE11.
87
var getRandomValues =
98
(typeof crypto != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) ||
109
(typeof msCrypto != 'undefined' &&
1110
typeof window.msCrypto.getRandomValues == 'function' &&
1211
msCrypto.getRandomValues.bind(msCrypto));
1312

14-
let rng;
15-
16-
if (getRandomValues) {
17-
// WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
18-
var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef
19-
20-
rng = function whatwgRNG() {
21-
getRandomValues(rnds8);
22-
return rnds8;
23-
};
24-
} else {
25-
// Math.random()-based (RNG)
26-
//
27-
// If all else fails, use Math.random(). It's fast, but is of unspecified
28-
// quality.
29-
var rnds = new Array(16);
30-
31-
rng = function mathRNG() {
32-
for (var i = 0, r; i < 16; i++) {
33-
if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
34-
rnds[i] = (r >>> ((i & 0x03) << 3)) & 0xff;
35-
}
36-
37-
return rnds;
38-
};
13+
var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef
14+
export default function rng() {
15+
if (!getRandomValues) {
16+
throw new Error(
17+
'uuid: This browser does not seem to support crypto.getRandomValues(). If you need to support this browser, please provide a custom random number generator through options.rng.',
18+
);
19+
}
20+
return getRandomValues(rnds8);
3921
}
40-
41-
export default rng;

src/rng.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import crypto from 'crypto';
22

3-
export default function nodeRNG() {
3+
export default function rng() {
44
return crypto.randomBytes(16);
55
}

src/v1.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function v1(options, buf, offset) {
2626
// specified. We do this lazily to minimize issues related to insufficient
2727
// system entropy. See #189
2828
if (node == null || clockseq == null) {
29-
var seedBytes = rng();
29+
var seedBytes = options.random || (options.rng || rng)();
3030
if (node == null) {
3131
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
3232
node = _nodeId = [

test/browser/ie.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ browserTest('ie', 9003, [
1414
os: 'Windows',
1515
os_version: '7',
1616
},
17+
{
18+
browserName: 'IE',
19+
browser_version: '8.0',
20+
os: 'Windows',
21+
os_version: '7',
22+
},
1723
]);

test/unit/unit.test.js

+5-12
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import v4 from '../../src/v4.js';
1111
import v5 from '../../src/v5.js';
1212

1313
describe('rng', () => {
14-
test('nodeRNG', () => {
15-
assert.equal(rng.name, 'nodeRNG');
16-
14+
test('Node.js RNG', () => {
1715
var bytes = rng();
1816
assert.equal(bytes.length, 16);
1917

@@ -22,15 +20,10 @@ describe('rng', () => {
2220
}
2321
});
2422

25-
test('mathRNG', () => {
26-
assert.equal(rngBrowser.name, 'mathRNG');
27-
28-
var bytes = rng();
29-
assert.equal(bytes.length, 16);
30-
31-
for (var i = 0; i < bytes.length; i++) {
32-
assert.equal(typeof bytes[i], 'number');
33-
}
23+
test('Browser without crypto.getRandomValues()', () => {
24+
assert.throws(() => {
25+
rngBrowser();
26+
});
3427
});
3528

3629
// Test of whatwgRNG missing for now since with esmodules we can no longer manipulate the

test/unit/v1-random.test.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import assert from 'assert';
2+
import v1 from '../../src/v1.js';
3+
4+
// Since the clockseq is cached in the module this test must run in a separate file in order to
5+
// initialize the v1 clockseq with controlled random data.
6+
describe('v1', () => {
7+
const randomBytesFixture = [
8+
0x10,
9+
0x91,
10+
0x56,
11+
0xbe,
12+
0xc4,
13+
0xfb,
14+
0xc1,
15+
0xea,
16+
0x71,
17+
0xb4,
18+
0xef,
19+
0xe1,
20+
0x67,
21+
0x1c,
22+
0x58,
23+
0x36,
24+
];
25+
26+
test('explicit options.random produces expected id', () => {
27+
const id = v1({
28+
msecs: 1321651533573,
29+
nsecs: 5432,
30+
random: randomBytesFixture,
31+
});
32+
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
33+
});
34+
});

test/unit/v1-rng.test.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import assert from 'assert';
2+
import v1 from '../../src/v1.js';
3+
4+
// Since the clockseq is cached in the module this test must run in a separate file in order to
5+
// initialize the v1 clockseq with controlled random data.
6+
describe('v1', () => {
7+
const randomBytesFixture = [
8+
0x10,
9+
0x91,
10+
0x56,
11+
0xbe,
12+
0xc4,
13+
0xfb,
14+
0xc1,
15+
0xea,
16+
0x71,
17+
0xb4,
18+
0xef,
19+
0xe1,
20+
0x67,
21+
0x1c,
22+
0x58,
23+
0x36,
24+
];
25+
26+
test('explicit options.random produces expected id', () => {
27+
const id = v1({
28+
msecs: 1321651533573,
29+
nsecs: 5432,
30+
rng: () => randomBytesFixture,
31+
});
32+
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
33+
});
34+
});

0 commit comments

Comments
 (0)