Skip to content

Commit

Permalink
feat: Remove default Node.js support
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Default Node.js support is removed. It is hard to use
different APIs in different environments right in today's bundled world.

Closes #116.
  • Loading branch information
luczsoma committed Oct 12, 2019
1 parent 5a02192 commit abd3174
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 426 deletions.
107 changes: 73 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,55 @@ TypeScript/JavaScript library for generating cryptographically strong, uniformly

<p>
<a href="https://travis-ci.org/Diplomatiq/crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://img.shields.io/travis/Diplomatiq/crypto-random.svg" alt="build status">
<img src="https://img.shields.io/travis/Diplomatiq/crypto-random.svg" alt="build status">
</a>

<a href="https://github.com/Diplomatiq/crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://img.shields.io/github/languages/top/Diplomatiq/crypto-random.svg" alt="languages used">
<img src="https://img.shields.io/github/languages/top/Diplomatiq/crypto-random.svg" alt="languages used">
</a>

<a href="https://www.npmjs.com/package/@diplomatiq/crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://img.shields.io/npm/dt/@diplomatiq/crypto-random.svg" alt="downloads from npm">
<img src="https://img.shields.io/npm/dt/@diplomatiq/crypto-random.svg" alt="downloads from npm">
</a>

<a href="https://www.npmjs.com/package/@diplomatiq/crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://img.shields.io/npm/v/@diplomatiq/crypto-random.svg" alt="latest released version on npm">
<img src="https://img.shields.io/npm/v/@diplomatiq/crypto-random.svg" alt="latest released version on npm">
</a>

<a href="https://github.com/Diplomatiq/crypto-random/blob/master/LICENSE" target="_blank" style="text-decoration: none;">
<img src="https://img.shields.io/npm/l/@diplomatiq/crypto-random.svg" alt="license">
<img src="https://img.shields.io/npm/l/@diplomatiq/crypto-random.svg" alt="license">
</a>
</p>

<p>
<a href="https://sonarcloud.io/dashboard?id=Diplomatiq_crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=alert_status" alt="Quality Gate">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=alert_status" alt="Quality Gate">
</a>

<a href="https://sonarcloud.io/dashboard?id=Diplomatiq_crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=coverage" alt="Coverage">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=coverage" alt="Coverage">
</a>

<a href="https://sonarcloud.io/dashboard?id=Diplomatiq_crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=sqale_rating" alt="Maintainability Rating">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=sqale_rating" alt="Maintainability Rating">
</a>

<a href="https://sonarcloud.io/dashboard?id=Diplomatiq_crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=reliability_rating" alt="Reliability Rating">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=reliability_rating" alt="Reliability Rating">
</a>

<a href="https://sonarcloud.io/dashboard?id=Diplomatiq_crypto-random" target="_blank" style="text-decoration: none;">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=security_rating" alt="Security Rating">
<img src="https://sonarcloud.io/api/project_badges/measure?project=Diplomatiq_crypto-random&metric=security_rating" alt="Security Rating">
</a>

<a href="https://github.com/Diplomatiq/crypto-random/pulls?utf8=✓&q=is%3Apr+is%3Aclosed+label%3Adependabot" target="_blank" style="text-decoration: none;">
<img src="https://api.dependabot.com/badges/status?host=github&repo=Diplomatiq/crypto-random" alt="Dependabot">
<img src="https://api.dependabot.com/badges/status?host=github&repo=Diplomatiq/crypto-random" alt="Dependabot">
</a>
</p>

<p>
<a href="https://gitter.im/Diplomatiq/crypto-random?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank" style="text-decoration: none;">
<img src="https://badges.gitter.im/Diplomatiq/crypto-random.svg" alt="Gitter">
<img src="https://badges.gitter.im/Diplomatiq/crypto-random.svg" alt="Gitter">
</a>
</p>

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -198,41 +198,80 @@ boolean(): Promise<boolean>;

## 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<T extends UnsignedTypedArray>(array: T): T | Promise<T>;
}
```

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<T extends UnsignedTypedArray>(array: T): Promise<T>;
### 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<T extends UnsignedTypedArray>(array: T): Promise<T> {
return new Promise<T>((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).
Expand Down
54 changes: 54 additions & 0 deletions src/browserEntropyProvider.ts
Original file line number Diff line number Diff line change
@@ -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<T extends UnsignedTypedArray>(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;
}
}
2 changes: 1 addition & 1 deletion src/entropyProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UnsignedTypedArray } from './unsignedTypedArray';

export interface EntropyProvider {
getRandomValues<T extends UnsignedTypedArray>(array: T): Promise<T>;
getRandomValues<T extends UnsignedTypedArray>(array: T): T | Promise<T>;
}
125 changes: 0 additions & 125 deletions src/environmentDetectingEntropyProvider.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/randomGenerator.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
}

Expand Down

0 comments on commit abd3174

Please sign in to comment.