Skip to content

Commit

Permalink
Replace Buffer with Uint8Array (#189)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
dreamorosi and sindresorhus authored Oct 24, 2023
1 parent d904833 commit ddcfa1d
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 13 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"dot-prop": "^7.2.0",
"env-paths": "^3.0.0",
"json-schema-typed": "^8.0.1",
"semver": "^7.3.8"
"semver": "^7.3.8",
"uint8array-extras": "^0.2.0"
},
"devDependencies": {
"@sindresorhus/tsconfig": "^3.0.1",
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ The only use-case I can think of is having the config located in the app directo

#### encryptionKey

Type: `string | Buffer | TypedArray | DataView`\
Type: `string | Uint8Array | TypedArray | DataView`\
Default: `undefined`

Note that this is **not intended for security purposes**, since the encryption key would be easily found inside a plain-text Node.js app.
Expand Down
21 changes: 14 additions & 7 deletions source/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-redundant-type-constituents */
import {isDeepStrictEqual} from 'node:util';
import process from 'node:process';
import {Buffer} from 'node:buffer';
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
Expand All @@ -15,6 +14,12 @@ import ajvFormatsModule from 'ajv-formats';
import debounceFn from 'debounce-fn';
import semver from 'semver';
import {type JSONSchema} from 'json-schema-typed';
import {
concatUint8Arrays,
isUint8Array,
stringToUint8Array,
uint8ArrayToString,
} from 'uint8array-extras';
import {
type Deserialize,
type Migrations,
Expand Down Expand Up @@ -57,7 +62,7 @@ export default class Conf<T extends Record<string, any> = Record<string, unknown
readonly path: string;
readonly events: EventEmitter;
readonly #validator?: AjvValidateFunction;
readonly #encryptionKey?: string | Buffer | NodeJS.TypedArray | DataView;
readonly #encryptionKey?: string | Uint8Array | NodeJS.TypedArray | DataView;
readonly #options: Readonly<Partial<Options<T>>>;
readonly #defaultValues: Partial<T> = {};

Expand Down Expand Up @@ -353,17 +358,19 @@ export default class Conf<T extends Record<string, any> = Record<string, unknown
}
}

private _encryptData(data: string | Buffer): string {
private _encryptData(data: string | Uint8Array): string {
if (!this.#encryptionKey) {
return data.toString();
return typeof data === 'string' ? data : uint8ArrayToString(data);
}

// Check if an initialization vector has been used to encrypt the data.
try {
const initializationVector = data.slice(0, 16);
const password = crypto.pbkdf2Sync(this.#encryptionKey, initializationVector.toString(), 10_000, 32, 'sha512');
const decipher = crypto.createDecipheriv(encryptionAlgorithm, password, initializationVector);
return Buffer.concat([decipher.update(Buffer.from(data.slice(17))), decipher.final()]).toString('utf8');
const slice = data.slice(17);
const dataUpdate = isUint8Array(slice) ? slice : stringToUint8Array(slice);
return uint8ArrayToString(concatUint8Arrays([decipher.update(dataUpdate), decipher.final()]));
} catch {}

return data.toString();
Expand Down Expand Up @@ -425,13 +432,13 @@ export default class Conf<T extends Record<string, any> = Record<string, unknown
}

private _write(value: T): void {
let data: string | Buffer = this._serialize(value);
let data: string | Uint8Array = this._serialize(value);

if (this.#encryptionKey) {
const initializationVector = crypto.randomBytes(16);
const password = crypto.pbkdf2Sync(this.#encryptionKey, initializationVector.toString(), 10_000, 32, 'sha512');
const cipher = crypto.createCipheriv(encryptionAlgorithm, password, initializationVector);
data = Buffer.concat([initializationVector, Buffer.from(':'), cipher.update(Buffer.from(data)), cipher.final()]);
data = concatUint8Arrays([initializationVector, stringToUint8Array(':'), cipher.update(stringToUint8Array(data)), cipher.final()]);
}

// Temporary workaround for Conf being packaged in a Ubuntu Snap app.
Expand Down
3 changes: 1 addition & 2 deletions source/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {type Buffer} from 'node:buffer';
import {type EventEmitter} from 'node:events';
import {type JSONSchema as TypedJSONSchema} from 'json-schema-typed';
// eslint-disable unicorn/import-index
Expand Down Expand Up @@ -132,7 +131,7 @@ export type Options<T extends Record<string, any>> = {
When specified, the store will be encrypted using the [`aes-256-cbc`](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation) encryption algorithm.
*/
encryptionKey?: string | Buffer | NodeJS.TypedArray | DataView;
encryptionKey?: string | Uint8Array | NodeJS.TypedArray | DataView;

/**
Extension of the config file.
Expand Down
4 changes: 2 additions & 2 deletions test/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-new, @typescript-eslint/naming-convention */
import {Buffer} from 'node:buffer';
import {stringToUint8Array} from 'uint8array-extras';
import {expectType, expectAssignable, expectError} from 'tsd';
import Conf from '../source/index.js';

Expand All @@ -23,7 +23,7 @@ new Conf<UnicornFoo>({configName: ''});
new Conf<UnicornFoo>({projectName: 'foo'});
new Conf<UnicornFoo>({cwd: ''});
new Conf<UnicornFoo>({encryptionKey: ''});
new Conf<UnicornFoo>({encryptionKey: Buffer.from('')});
new Conf<UnicornFoo>({encryptionKey: stringToUint8Array('')});
new Conf<UnicornFoo>({encryptionKey: new Uint8Array([1])});
new Conf<UnicornFoo>({encryptionKey: new DataView(new ArrayBuffer(2))});
new Conf<UnicornFoo>({fileExtension: '.foo'});
Expand Down

0 comments on commit ddcfa1d

Please sign in to comment.