Skip to content

Commit

Permalink
feat: remove insecure fallback random number generator
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ctavan committed Jan 27, 2020
1 parent 4802e8b commit 3a5842b
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 55 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Features:
- Support for version 1, 3, 4 and 5 UUIDs
- Cross-platform: CommonJS build for Node.js and [ECMAScript Modules](#ecmascript-modules) for the
browser.
- Uses cryptographically-strong random number APIs (when available)
- Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883))
- Uses cryptographically-strong random number APIs
- Zero-dependency, small footprint

## Quickstart - Node.js/CommonJS

Expand Down Expand Up @@ -201,12 +201,12 @@ uuid.v1(options, buffer, offset);
Generate and return a RFC4122 v1 (timestamp-based) UUID.

- `options` - (Object) Optional uuid state to apply. Properties may include:

- `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1.
- `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used.
- `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used.
- `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.

- `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`.
- `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`.
- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
- `offset` - (Number) Starting index in `buffer` at which to begin writing.

Expand Down
8 changes: 4 additions & 4 deletions README_js.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Features:
- Support for version 1, 3, 4 and 5 UUIDs
- Cross-platform: CommonJS build for Node.js and [ECMAScript Modules](#ecmascript-modules) for the
browser.
- Uses cryptographically-strong random number APIs (when available)
- Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883))
- Uses cryptographically-strong random number APIs
- Zero-dependency, small footprint

## Quickstart - Node.js/CommonJS

Expand Down Expand Up @@ -196,12 +196,12 @@ uuid.v1(options, buffer, offset);
Generate and return a RFC4122 v1 (timestamp-based) UUID.

- `options` - (Object) Optional uuid state to apply. Properties may include:

- `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1.
- `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used.
- `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used.
- `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.

- `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`.
- `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`.
- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
- `offset` - (Number) Starting index in `buffer` at which to begin writing.

Expand Down
46 changes: 13 additions & 33 deletions src/rng-browser.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,21 @@
// Unique ID creation requires a high quality random # generator. In the
// browser this is a little complicated due to unknown quality of Math.random()
// and inconsistent support for the `crypto` API. We do the best we can via
// feature-detection
// Unique ID creation requires a high quality random # generator. In the browser we therefore
// require the crypto API and do not support built-in fallback to lower quality random number
// generators (like Math.random()).

// getRandomValues needs to be invoked in a context where "this" is a Crypto
// implementation. Also, find the complete implementation of crypto on IE11.
// getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. Also,
// find the complete implementation of crypto (msCrypto) on IE11.
var getRandomValues =
(typeof crypto != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) ||
(typeof msCrypto != 'undefined' &&
typeof window.msCrypto.getRandomValues == 'function' &&
msCrypto.getRandomValues.bind(msCrypto));

let rng;

if (getRandomValues) {
// WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef

rng = function whatwgRNG() {
getRandomValues(rnds8);
return rnds8;
};
} else {
// Math.random()-based (RNG)
//
// If all else fails, use Math.random(). It's fast, but is of unspecified
// quality.
var rnds = new Array(16);

rng = function mathRNG() {
for (var i = 0, r; i < 16; i++) {
if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
rnds[i] = (r >>> ((i & 0x03) << 3)) & 0xff;
}

return rnds;
};
var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef
export default function rng() {
if (!getRandomValues) {
throw new Error(
'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.',
);
}
return getRandomValues(rnds8);
}

export default rng;
2 changes: 1 addition & 1 deletion src/rng.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import crypto from 'crypto';

export default function nodeRNG() {
export default function rng() {
return crypto.randomBytes(16);
}
2 changes: 1 addition & 1 deletion src/v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function v1(options, buf, offset) {
// specified. We do this lazily to minimize issues related to insufficient
// system entropy. See #189
if (node == null || clockseq == null) {
var seedBytes = rng();
var seedBytes = options.random || (options.rng || rng)();
if (node == null) {
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
node = _nodeId = [
Expand Down
17 changes: 5 additions & 12 deletions test/unit/rng.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import rng from '../../src/rng.js';
import rngBrowser from '../../src/rng-browser.js';

describe('rng', () => {
test('nodeRNG', () => {
assert.equal(rng.name, 'nodeRNG');

test('Node.js RNG', () => {
var bytes = rng();
assert.equal(bytes.length, 16);

Expand All @@ -14,15 +12,10 @@ describe('rng', () => {
}
});

test('mathRNG', () => {
assert.equal(rngBrowser.name, 'mathRNG');

var bytes = rng();
assert.equal(bytes.length, 16);

for (var i = 0; i < bytes.length; i++) {
assert.equal(typeof bytes[i], 'number');
}
test('Browser without crypto.getRandomValues()', () => {
assert.throws(() => {
rngBrowser();
});
});

// Test of whatwgRNG missing for now since with esmodules we can no longer manipulate the
Expand Down
34 changes: 34 additions & 0 deletions test/unit/v1-random.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import assert from 'assert';
import v1 from '../../src/v1.js';

// Since the clockseq is cached in the module this test must run in a separate file in order to
// initialize the v1 clockseq with controlled random data.
describe('v1', () => {
const randomBytesFixture = [
0x10,
0x91,
0x56,
0xbe,
0xc4,
0xfb,
0xc1,
0xea,
0x71,
0xb4,
0xef,
0xe1,
0x67,
0x1c,
0x58,
0x36,
];

test('explicit options.random produces expected id', () => {
const id = v1({
msecs: 1321651533573,
nsecs: 5432,
random: randomBytesFixture,
});
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
});
});
34 changes: 34 additions & 0 deletions test/unit/v1-rng.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import assert from 'assert';
import v1 from '../../src/v1.js';

// Since the clockseq is cached in the module this test must run in a separate file in order to
// initialize the v1 clockseq with controlled random data.
describe('v1', () => {
const randomBytesFixture = [
0x10,
0x91,
0x56,
0xbe,
0xc4,
0xfb,
0xc1,
0xea,
0x71,
0xb4,
0xef,
0xe1,
0x67,
0x1c,
0x58,
0x36,
];

test('explicit options.random produces expected id', () => {
const id = v1({
msecs: 1321651533573,
nsecs: 5432,
rng: () => randomBytesFixture,
});
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
});
});

0 comments on commit 3a5842b

Please sign in to comment.