diff --git a/README.md b/README.md index c0fe4fb..79708f2 100644 --- a/README.md +++ b/README.md @@ -4,55 +4,55 @@ TypeScript/JavaScript library for generating cryptographically strong, uniformly

- build status + build status - languages used + languages used - downloads from npm + downloads from npm - latest released version on npm + latest released version on npm - license + license

- Quality Gate + Quality Gate - Coverage + Coverage - Maintainability Rating + Maintainability Rating - Reliability Rating + Reliability Rating - Security Rating + Security Rating - Dependabot + Dependabot

- Gitter + Gitter

@@ -88,13 +88,13 @@ import { RandomGenerator } from '@diplomatiq/crypto-random'; // … async function main() { - const randomGenerator = new RandomGenerator(); - const randomString = await randomGenerator.alphanumeric(32); - // randomString will contain a 32-character-long alphanumeric string + const randomGenerator = new RandomGenerator(); + const randomString = await randomGenerator.alphanumeric(32); + // randomString will contain a 32-character-long alphanumeric string } ``` -Node.js and browser environments are both supported. For more information, see the **Entropy sources** section below. +From version 2.0, only browser environments are supported out of the box (the default entropy source being `window.crypto.getRandomValues`). But with minimal additional work, you can inject any other entropy source (e.g. for using crypto-random in a Node.js environment). For more information, see the **Entropy sources** section below. ## API @@ -198,41 +198,80 @@ boolean(): Promise; ## Entropy sources -### Used entropy sources +### Default entropy source -In a web browser environment, `window.crypto.getRandomValues`, in a Node.js environment, `crypto.randomFill` is used as the underlying CSPRNG. This is automatically detected by the `RandomGenerator`. +Providing no arguments in the constructor, the `RandomGenerator` is instantiated using the default `BrowserEntropyProvider` as its entropy source. This will look for `window.crypto.getRandomValues`. -### Using a custom entropy source +``` +type UnsignedTypedArray = Uint8Array | Uint16Array | Uint32Array; +``` -**WARNING!** Unless you are a seasoned cryptography expert possessing comprehensive knowledge about random/pseudo-random value generation, **DO NOT use any custom entropy source implementation other than the default**, or found in well-tested, popular libraries survived many years under public scrutiny. Cryptography — and mostly random generation — can be messed up very easily. If you use anything else than a CSPRNG/TRNG for gathering entropy, the values you generate using that entropy source will not be random in the cryptographic meaning, and thus will NOT be suitable for being used as passwords/keys/nonces/etc. +``` +interface EntropyProvider { + getRandomValues(array: T): T | Promise; +} +``` -Providing no arguments in the constructor, the `RandomGenerator` is instantiated using the default `EnvironmentDetectingEntropyProvider` as its entropy source. This detects if the code is run in a web browser or in a Node.js process, and uses the available cryptography API on the given platform as its underlying random source. (As stated above: in a web browser, `window.crypto.getRandomValues`, in Node.js, `crypto.randomFill` is used.) +``` +class RandomGenerator { + /** + * Provides entropy in the form of random-filled typed arrays. + */ + private readonly entropyProvider: EntropyProvider; -As long as it implements the `EntropyProvider` interface specified below, you can use any kind of entropy source by providing it to the constructor at instantiating the `RandomGenerator`. + constructor(entropyProvider: EntropyProvider = new BrowserEntropyProvider()) { + this.entropyProvider = entropyProvider; + } + // … +} ``` -type UnsignedTypedArray = Uint8Array | Uint16Array | Uint32Array; -interface EntropyProvider { - getRandomValues(array: T): Promise; +### Using other entropy sources + +You can inject any entropy source into the `RandomGenerator` as long as it implements the required `EntropyProvider` interface specified above. + +E.g. in your Node.js application, you can create `nodeJsEntropyProvider.ts`: + +``` +import { EntropyProvider, UnsignedTypedArray } from '@diplomatiq/crypto-random'; +import { randomFill } from 'crypto'; + +export class NodeJsEntropyProvider implements EntropyProvider { + public async getRandomValues(array: T): Promise { + return new Promise((resolve, reject): void => { + randomFill(array, (error: Error | null, array: T) => { + if (error !== null) { + reject(error); + return; + } + resolve(array); + }); + }); + } } ``` +And then (still in your Node.js application) use `RandomGenerator` as follows: + ``` -class RandomGenerator { - /** - * Provides entropy in the form of random-filled typed arrays. - */ - private readonly entropyProvider: EntropyProvider; +import { RandomGenerator } from '@diplomatiq/crypto-random'; +import { NodeJsEntropyProvider } from './nodeJsEntropyProvider'; - constructor(entropyProvider: EntropyProvider = new EnvironmentDetectingEntropyProvider()) { - this.entropyProvider = entropyProvider; - } +// … - // … +async function main() { + const entropyProvider = new NodeJsEntropyProvider(); + const randomGenerator = new RandomGenerator(entropyProvider); + const randomString = await randomGenerator.alphanumeric(32); + // randomString will contain a 32-character-long alphanumeric string } ``` +### Using a custom entropy source + +**WARNING!** Unless you are a seasoned cryptography expert possessing comprehensive knowledge about random/pseudo-random value generation, **DO NOT use any custom entropy source implementation other than the default**, or found in well-tested, popular _cryptographic_ libraries survived many years under public scrutiny. Cryptography — and mostly random generation — can be messed up very easily. If you use anything else than a CSPRNG/TRNG for gathering entropy, the values you generate using that entropy source will not be random in the cryptographic meaning, and thus will NOT be suitable for being used as passwords/keys/nonces/etc. + ## Discrete uniform distribution In this library's context, discrete uniform distribution means that any character from a given alphabet will be chosen with equal probability into the generated random value. At generating any kind of cryptographic keys (passwords, authentication tokens, nonces), uniform distribution is crucial: in every other case the size of the key space decreases in some degree (thus finding the key is easier). diff --git a/src/browserEntropyProvider.ts b/src/browserEntropyProvider.ts new file mode 100644 index 0000000..06c8ef9 --- /dev/null +++ b/src/browserEntropyProvider.ts @@ -0,0 +1,54 @@ +import { EntropyProvider } from './entropyProvider'; +import { UnsignedTypedArray } from './unsignedTypedArray'; + +export class BrowserEntropyProvider implements EntropyProvider { + /** + * The crypto implementation used in the browser. The window.crypto object is used. + */ + private readonly crypto?: Crypto; + + /** + * According to the Web Crypto standard, there is a 2 ** 16 bytes quota for requesting entropy at once. + */ + private static readonly BROWSER_ENTROPY_QUOTA_BYTES = 65536; + + public constructor() { + if ( + typeof window === 'undefined' || + typeof window.crypto === 'undefined' || + typeof window.crypto.getRandomValues === 'undefined' + ) { + throw new Error('window.crypto.getRandomValues is not available'); + } + + this.crypto = window.crypto; + } + + /** + * Puts random values into the given @param array, and returns the array. + * If the array's length is greater than the general @member BROWSER_ENTROPY_QUOTA_BYTES, + * it is divided into chunks, and filled chunk-by-chunk. + */ + public getRandomValues(array: T): T { + if (this.crypto === undefined) { + throw new Error('AssertError: no crypto'); + } + + if (array.byteLength <= BrowserEntropyProvider.BROWSER_ENTROPY_QUOTA_BYTES) { + return this.crypto.getRandomValues(array); + } + + let remainingBytes = array.byteLength; + while (remainingBytes > 0) { + const availableEntropyBytes = Math.min(remainingBytes, BrowserEntropyProvider.BROWSER_ENTROPY_QUOTA_BYTES); + const chunkStart = array.byteLength - remainingBytes; + const chunkLength = availableEntropyBytes / array.BYTES_PER_ELEMENT; + const chunkEnd = chunkStart + chunkLength; + const chunkToFill = array.subarray(chunkStart, chunkEnd); + this.crypto.getRandomValues(chunkToFill); + remainingBytes -= availableEntropyBytes; + } + + return array; + } +} diff --git a/src/entropyProvider.ts b/src/entropyProvider.ts index 783517c..17300e1 100644 --- a/src/entropyProvider.ts +++ b/src/entropyProvider.ts @@ -1,5 +1,5 @@ import { UnsignedTypedArray } from './unsignedTypedArray'; export interface EntropyProvider { - getRandomValues(array: T): Promise; + getRandomValues(array: T): T | Promise; } diff --git a/src/environmentDetectingEntropyProvider.ts b/src/environmentDetectingEntropyProvider.ts deleted file mode 100644 index 6e48254..0000000 --- a/src/environmentDetectingEntropyProvider.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { EntropyProvider } from './entropyProvider'; -import { UnsignedTypedArray } from './unsignedTypedArray'; - -export class EnvironmentDetectingEntropyProvider implements EntropyProvider { - /** - * The crypto implementation used in the browser. The window.crypto object is used. - */ - private readonly browserCrypto?: Crypto; - - /** - * The crypto implementation used in Node.js environments. This will be provided by the "crypto" module of Node. - */ - private readonly nodeCrypto?: { - randomFill: (buffer: T, callback: (err: Error | null, buf: T) => void) => void; - }; - - /** - * According to the Web Crypto standard, there is a 2 ** 16 bytes quota for requesting entropy. - */ - private static readonly BROWSER_ENTROPY_QUOTA_BYTES = 65536; - - /** - * The environment in which the package is run. - */ - private readonly environment: 'browser' | 'node'; - - public constructor() { - if (typeof window !== 'undefined' && typeof window.document !== 'undefined') { - this.environment = 'browser'; - if (typeof window.crypto !== 'undefined' && typeof window.crypto.getRandomValues !== 'undefined') { - this.browserCrypto = window.crypto; - } else { - throw new Error('window.crypto.getRandomValues not available'); - } - } else if ( - typeof process !== 'undefined' && - typeof process.versions !== undefined && - typeof process.versions.node !== undefined - ) { - this.environment = 'node'; - try { - this.nodeCrypto = require('crypto'); - } catch (e) { - throw new Error('NodeJS.crypto not available'); - } - } else { - throw new Error('Unexpected environment: neither browser nor node'); - } - } - - /** - * Puts random values into the given @param array, and returns the array. - */ - public async getRandomValues(array: T): Promise { - switch (this.environment) { - case 'browser': - return this.getRandomValuesBrowser(array); - case 'node': - return this.getRandomValuesNode(array); - } - - throw new Error('AssertError: unexpected environment in getRandomValues'); - } - - /** - * Puts random values into the given @param array in a browser environment, and returns the array. - * If the array's length is greater than the general @member BROWSER_ENTROPY_QUOTA_BYTES, - * it is divided into chunks, and filled chunk-by-chunk. - * Can be only called, if @member environment is 'browser'. - */ - private getRandomValuesBrowser(array: T): T { - if (this.environment !== 'browser') { - throw new Error('AssertError: not in browser environment'); - } - - if (this.browserCrypto === undefined) { - throw new Error('AssertError: no browserCrypto in browser environment'); - } - - if (array.byteLength <= EnvironmentDetectingEntropyProvider.BROWSER_ENTROPY_QUOTA_BYTES) { - return this.browserCrypto.getRandomValues(array); - } - - let remainingBytes = array.byteLength; - while (remainingBytes > 0) { - const availableEntropyBytes = Math.min( - remainingBytes, - EnvironmentDetectingEntropyProvider.BROWSER_ENTROPY_QUOTA_BYTES, - ); - const chunkStart = array.byteLength - remainingBytes; - const chunkLength = availableEntropyBytes / array.BYTES_PER_ELEMENT; - const chunkEnd = chunkStart + chunkLength; - const chunkToFill = array.subarray(chunkStart, chunkEnd); - this.browserCrypto.getRandomValues(chunkToFill); - remainingBytes -= availableEntropyBytes; - } - - return array; - } - - /** - * Puts random values into the given @param array in a Node.js environment, and returns the array. - * Can be only called, if @member environment is 'node'. - */ - private async getRandomValuesNode(array: T): Promise { - if (this.environment !== 'node') { - throw new Error('not in node environment'); - } - - return new Promise((resolve, reject): void => { - if (this.nodeCrypto === undefined) { - throw new Error('AssertError: no nodeCrypto in node environment'); - } - - this.nodeCrypto.randomFill(array, (error: Error | null, array: T) => { - if (error !== null) { - reject(error); - return; - } - - resolve(array); - }); - }); - } -} diff --git a/src/randomGenerator.ts b/src/randomGenerator.ts index 234c34b..df28e03 100644 --- a/src/randomGenerator.ts +++ b/src/randomGenerator.ts @@ -1,7 +1,7 @@ import { Alphabets } from './alphabets'; +import { BrowserEntropyProvider } from './browserEntropyProvider'; import { ConfigurableUniquenessStore } from './configurableUniquenessStore'; import { EntropyProvider } from './entropyProvider'; -import { EnvironmentDetectingEntropyProvider } from './environmentDetectingEntropyProvider'; import { RandomGeneratorErrorCodes } from './randomGeneratorErrorCodes'; import { UnsignedTypedArray } from './unsignedTypedArray'; @@ -17,7 +17,7 @@ export class RandomGenerator { */ private readonly entropyProvider: EntropyProvider; - public constructor(entropyProvider: EntropyProvider = new EnvironmentDetectingEntropyProvider()) { + public constructor(entropyProvider: EntropyProvider = new BrowserEntropyProvider()) { this.entropyProvider = entropyProvider; } diff --git a/test/specs/browserEntropyProvider.test.ts b/test/specs/browserEntropyProvider.test.ts new file mode 100644 index 0000000..d120227 --- /dev/null +++ b/test/specs/browserEntropyProvider.test.ts @@ -0,0 +1,96 @@ +import { expect } from 'chai'; +import { beforeEach } from 'mocha'; +import { BrowserEntropyProvider } from '../../src/browserEntropyProvider'; +import { EntropyProvider } from '../../src/entropyProvider'; +import { windowMock } from '../utils/windowMock'; + +describe('BrowserEntropyProvider', () => { + let entropyProvider: EntropyProvider; + + before(() => { + // @ts-ignore + global.window = windowMock(); + }); + + after(() => { + // @ts-ignore + global.window = undefined; + }); + + beforeEach(() => { + entropyProvider = new BrowserEntropyProvider(); + }); + + it('should work', async () => { + const array = new Uint8Array(10); + expect(array.every(v => v === 0)).to.be.true; + const sameArray = await entropyProvider.getRandomValues(array); + expect(array).to.deep.equal(sameArray); + expect(array.some(v => v !== 0)).to.be.true; + }); + + it('should work for arrays larger than 65536 bytes', async () => { + const array = new Uint8Array(100000); + expect(array.every(v => v === 0)).to.be.true; + const sameArray = await entropyProvider.getRandomValues(array); + expect(array).to.deep.equal(sameArray); + expect(array.some(v => v !== 0)).to.be.true; + expect(array.subarray(80000, 90000).some(v => v !== 0)).to.be.true; + }); + + it('should throw if window is not available', () => { + // @ts-ignore + global.window = undefined; + + try { + new BrowserEntropyProvider(); + expect.fail('did not throw'); + } catch (e) { + expect(e.message).to.equal('window.crypto.getRandomValues is not available'); + } + + // @ts-ignore + global.window = windowMock(); + }); + + it('should throw if window.crypto is not available', () => { + // @ts-ignore + global.window.crypto = undefined; + + try { + new BrowserEntropyProvider(); + expect.fail('did not throw'); + } catch (e) { + expect(e.message).to.equal('window.crypto.getRandomValues is not available'); + } + + // @ts-ignore + global.window = windowMock(); + }); + + it('should throw if window.crypto.getRandomValues is not available', () => { + // @ts-ignore + global.window.crypto.getRandomValues = undefined; + + try { + new BrowserEntropyProvider(); + expect.fail('did not throw'); + } catch (e) { + expect(e.message).to.equal('window.crypto.getRandomValues is not available'); + } + + // @ts-ignore + global.window = windowMock(); + }); + + it('should throw if browserCrypto is undefined', async () => { + // @ts-ignore + entropyProvider.crypto = undefined; + + try { + await entropyProvider.getRandomValues(new Uint8Array(1)); + } catch (e) { + expect(e.message).to.equal('AssertError: no crypto'); + } + }); +}); diff --git a/test/specs/confgurableUniquenessStore.test.ts b/test/specs/confgurableUniquenessStore.test.ts index 6a90966..a0a8721 100644 --- a/test/specs/confgurableUniquenessStore.test.ts +++ b/test/specs/confgurableUniquenessStore.test.ts @@ -6,7 +6,7 @@ describe('ConfigurableUniquenessStore', () => { describe('with unique = false', () => { beforeEach(() => { - configurableUniquenessStore = new ConfigurableUniquenessStore(false); + configurableUniquenessStore = new ConfigurableUniquenessStore(false); }); it('should add non-unique values', () => { @@ -26,7 +26,7 @@ describe('ConfigurableUniquenessStore', () => { describe('with unique = true', () => { beforeEach(() => { - configurableUniquenessStore = new ConfigurableUniquenessStore(true); + configurableUniquenessStore = new ConfigurableUniquenessStore(true); }); it('should not add non-unique values', () => { diff --git a/test/specs/environmentDetectingEntropyProvider.test.ts b/test/specs/environmentDetectingEntropyProvider.test.ts deleted file mode 100644 index 00bed26..0000000 --- a/test/specs/environmentDetectingEntropyProvider.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { expect } from 'chai'; -import { randomFillSync } from 'crypto'; -import { beforeEach } from 'mocha'; -import { EnvironmentDetectingEntropyProvider } from '../../src/environmentDetectingEntropyProvider'; -import { UnsignedTypedArray } from '../../src/unsignedTypedArray'; - -describe('EnvironmentDetectingEntropyProvider', () => { - let entropyProvider: EnvironmentDetectingEntropyProvider; - - describe('in browser environment', () => { - const windowMock = () => ({ - document: {}, - crypto: { - getRandomValues: (array: Uint8Array) => randomFillSync(array), - }, - }); - - before(() => { - // @ts-ignore - global.window = windowMock(); - }); - - after(() => { - // @ts-ignore - global.window = undefined; - }); - - beforeEach(() => { - entropyProvider = new EnvironmentDetectingEntropyProvider(); - }); - - it('should detect browser environment if global.window is defined', () => { - // @ts-ignore - expect(entropyProvider.environment).to.equal('browser'); - }); - - it('should work', async () => { - const array = new Uint8Array(10); - expect(array.every(v => v === 0)).to.be.true; - const sameArray = await entropyProvider.getRandomValues(array); - expect(array).to.deep.equal(sameArray); - expect(array.some(v => v !== 0)).to.be.true; - }); - - it('should work for arrays larger than 65536 bytes', async () => { - const array = new Uint8Array(100000); - expect(array.every(v => v === 0)).to.be.true; - const sameArray = await entropyProvider.getRandomValues(array); - expect(array).to.deep.equal(sameArray); - expect(array.some(v => v !== 0)).to.be.true; - }); - - it('should throw if window.crypto is not available', () => { - // @ts-ignore - window.crypto = undefined; - - try { - new EnvironmentDetectingEntropyProvider(); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal('window.crypto.getRandomValues not available'); - } - - // @ts-ignore - global.window = windowMock(); - }); - - it('should throw if window.crypto.getRandomValues is not available', () => { - // @ts-ignore - global.window.crypto.getRandomValues = undefined; - - try { - new EnvironmentDetectingEntropyProvider(); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal('window.crypto.getRandomValues not available'); - } - - // @ts-ignore - global.window = windowMock(); - }); - - it('should throw if calling the node method', async () => { - try { - // @ts-ignore - await entropyProvider.getRandomValuesNode(new Uint8Array()); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal('not in node environment'); - } - }); - }); - - describe('in Node.js environment', () => { - beforeEach(() => { - entropyProvider = new EnvironmentDetectingEntropyProvider(); - }); - - it('should detect node environment if global.window is not, but global.process is defined', () => { - // @ts-ignore - expect(entropyProvider.environment).to.equal('node'); - }); - - it('should work', async () => { - const array = new Uint8Array(10); - expect(array.every(v => v === 0)).to.be.true; - const sameArray = await entropyProvider.getRandomValues(array); - expect(array).to.deep.equal(sameArray); - expect(array.some(v => v !== 0)).to.be.true; - }); - - it('should throw if randomFill throws sync error', async () => { - try { - // @ts-ignore - await entropyProvider.getRandomValues(5); - expect.fail('did not throw'); - } catch (e) { - expect(e.code).to.equal('ERR_INVALID_ARG_TYPE'); - } - }); - - it('should throw if randomFill throws async error', async () => { - const simulatedErrorMessage = 'SimulatedError'; - - const Module = require('module'); - const originalRequire = Module.prototype.require; - Module.prototype.require = (requiredModuleName: string) => { - if (requiredModuleName === 'crypto') { - return { - randomFill: ( - array: T, - callback: (error: Error, array: T) => void, - ) => { - callback(new Error(simulatedErrorMessage), array); - }, - }; - } - - return originalRequire(requiredModuleName); - }; - - try { - const entropyProviderWithOverriddenNodeRequire = new EnvironmentDetectingEntropyProvider(); - await entropyProviderWithOverriddenNodeRequire.getRandomValues(new Uint8Array()); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal(simulatedErrorMessage); - } - - Module.prototype.require = originalRequire; - }); - - it('should throw if NodeJS.crypto is not available', () => { - const Module = require('module'); - const originalRequire = Module.prototype.require; - Module.prototype.require = (requiredModuleName: string) => { - if (requiredModuleName === 'crypto') { - throw new Error(); - } - }; - - try { - new EnvironmentDetectingEntropyProvider(); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal('NodeJS.crypto not available'); - } - - Module.prototype.require = originalRequire; - }); - - it('should throw if calling the browser method', async () => { - try { - // @ts-ignore - await entropyProvider.getRandomValuesBrowser(); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal('AssertError: not in browser environment'); - } - }); - }); - - describe('in unexpected environment', () => { - let process: NodeJS.Process; - - before(() => { - process = global.process; - // @ts-ignore - global.window = undefined; - // @ts-ignore - global.process = undefined; - }); - - after(() => { - global.process = process; - }); - - it('should throw', () => { - try { - new EnvironmentDetectingEntropyProvider(); - expect.fail('did not throw'); - } catch (e) { - expect(e.message).to.equal('Unexpected environment: neither browser nor node'); - } - }); - }); - - describe('code coverage supplementary tests', () => { - let entropyProvider: EnvironmentDetectingEntropyProvider; - - beforeEach(() => { - entropyProvider = new EnvironmentDetectingEntropyProvider(); - }); - - describe('getRandomVaules', () => { - it('should throw in an unexpected environment', async () => { - // @ts-ignore - entropyProvider.environment = 'Definitely Unexpected Environment'; - - try { - await entropyProvider.getRandomValues(new Uint8Array(1)); - } catch (e) { - expect(e.message).to.equal('AssertError: unexpected environment in getRandomValues'); - } - }); - }); - - describe('getRandomValuesBrowser', () => { - it('should throw if browserCrypto is undefined', async () => { - // @ts-ignore - entropyProvider.environment = 'browser'; - - try { - // @ts-ignore - await entropyProvider.getRandomValuesBrowser(new Uint8Array(1)); - } catch (e) { - expect(e.message).to.equal('AssertError: no browserCrypto in browser environment'); - } - }); - }); - - describe('getRandomValuesNode', () => { - it('should throw if nodeCrypto is undefined', async () => { - // @ts-ignore - entropyProvider.environment = 'node'; - - // @ts-ignore - entropyProvider.nodeCrypto = undefined; - - try { - // @ts-ignore - await entropyProvider.getRandomValuesNode(new Uint8Array(1)); - } catch (e) { - expect(e.message).to.equal('AssertError: no nodeCrypto in node environment'); - } - }); - }); - }); -}); diff --git a/test/specs/randomGenerator.test.ts b/test/specs/randomGenerator.test.ts index 675a7b7..fd2e451 100644 --- a/test/specs/randomGenerator.test.ts +++ b/test/specs/randomGenerator.test.ts @@ -1,17 +1,28 @@ import { expect } from 'chai'; import { SinonSpy, spy } from 'sinon'; +import { BrowserEntropyProvider } from '../../src/browserEntropyProvider'; import { EntropyProvider } from '../../src/entropyProvider'; -import { EnvironmentDetectingEntropyProvider } from '../../src/environmentDetectingEntropyProvider'; import { RandomGenerator } from '../../src/randomGenerator'; import { UnsignedTypedArray } from '../../src/unsignedTypedArray'; +import { windowMock } from '../utils/windowMock'; describe('RandomGenerator', () => { let entropyProvider: EntropyProvider; let getRandomValuesSpy: SinonSpy<[UnsignedTypedArray]>; let randomGeneratorInstance: RandomGenerator; + before(() => { + // @ts-ignore + global.window = windowMock(); + }); + + after(() => { + // @ts-ignore + global.window = undefined; + }); + beforeEach(() => { - entropyProvider = new EnvironmentDetectingEntropyProvider(); + entropyProvider = new BrowserEntropyProvider(); getRandomValuesSpy = spy(entropyProvider, 'getRandomValues'); randomGeneratorInstance = new RandomGenerator(entropyProvider); }); diff --git a/test/specs/uniformDistribution.test.ts b/test/specs/uniformDistribution.test.ts index 83a55ea..79df0d9 100644 --- a/test/specs/uniformDistribution.test.ts +++ b/test/specs/uniformDistribution.test.ts @@ -1,12 +1,27 @@ import { expect } from 'chai'; import { RandomGenerator } from '../../src/randomGenerator'; import { ChiSquaredTest } from '../utils/chiSquaredTest'; +import { windowMock } from '../utils/windowMock'; describe('Generated values should follow a uniform distribution', () => { // Setting unique = true would not really have a meaning here, since the output would be biased. const unique = false; - const randomGeneratorInstance = new RandomGenerator(); + let randomGeneratorInstance: RandomGenerator; + + before(() => { + // @ts-ignore + global.window = windowMock(); + }); + + after(() => { + // @ts-ignore + global.window = undefined; + }); + + beforeEach(() => { + randomGeneratorInstance = new RandomGenerator(); + }); describe('alphabetLength = 2', () => { const alphabetLength = 2; diff --git a/test/utils/windowMock.ts b/test/utils/windowMock.ts new file mode 100644 index 0000000..33cb054 --- /dev/null +++ b/test/utils/windowMock.ts @@ -0,0 +1,7 @@ +import { randomFillSync } from 'crypto'; + +export const windowMock = () => ({ + crypto: { + getRandomValues: (array: Uint8Array) => randomFillSync(array), + }, +});