Upgraded encryption using CipherIV (#322)
* Remove package-lock.json from gitignore * Update dependencies and fix repo url * Fix test * Update to cipheriv * Bump version * Sync package-lock * Revert extraneous package changes * Revert minor doc change
This commit is contained in:
parent
10318c0098
commit
d582066743
8 changed files with 94 additions and 33 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,8 +6,8 @@ test/fixtures/*.json
|
|||
!test/fixtures/bom.json
|
||||
!test/fixtures/no-bom.json
|
||||
!test/fixtures/secure.json
|
||||
!test/fixtures/secure-iv.json
|
||||
node_modules/
|
||||
node_modules/*
|
||||
npm-debug.log
|
||||
coverage
|
||||
package-lock.json
|
||||
|
|
10
README.md
10
README.md
|
@ -230,7 +230,7 @@ The input `obj` contains two properties passed in the following format:
|
|||
|
||||
The transformation function may alter both the key and the value.
|
||||
|
||||
The function may return either an object in the asme format as the input or a value that evaluates to false.
|
||||
The function may return either an object in the same format as the input or a value that evaluates to false.
|
||||
If the return value is falsey, the entry will be dropped from the store, otherwise it will replace the original key/value.
|
||||
|
||||
*Note: If the return value doesn't adhere to the above rules, an exception will be thrown.*
|
||||
|
@ -397,17 +397,19 @@ nconf.file('secure-file', {
|
|||
})
|
||||
```
|
||||
|
||||
This will encrypt each key using [`crypto.createCipher`](https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password), defaulting to `aes-256-ctr`. The encrypted file contents will look like this:
|
||||
This will encrypt each key using [`crypto.createCipheriv`](https://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv_options), defaulting to `aes-256-ctr`. The encrypted file contents will look like this:
|
||||
|
||||
```
|
||||
{
|
||||
"config-key-name": {
|
||||
"alg": "aes-256-ctr", // cipher used
|
||||
"value": "af07fbcf" // encrypted contents
|
||||
"value": "af07fbcf", // encrypted contents
|
||||
"iv": "49e7803a2a5ef98c7a51a8902b76dd10" // initialization vector
|
||||
},
|
||||
"another-config-key": {
|
||||
"alg": "aes-256-ctr", // cipher used
|
||||
"value": "e310f6d94f13" // encrypted contents
|
||||
"value": "e310f6d94f13", // encrypted contents
|
||||
"iv": "b654e01aed262f37d0acf200be193985" // initialization vector
|
||||
},
|
||||
}
|
||||
```
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
util = require('util'),
|
||||
Secure = require('secure-keys'),
|
||||
crypto = require('crypto'),
|
||||
formats = require('../formats'),
|
||||
Memory = require('./memory').Memory;
|
||||
|
||||
|
@ -50,12 +50,6 @@ var File = exports.File = function (options) {
|
|||
if (!this.secure.secret) {
|
||||
throw new Error('secure.secret option is required');
|
||||
}
|
||||
|
||||
this.keys = new Secure({
|
||||
secret: this.secure.secret,
|
||||
alg: this.secure.alg,
|
||||
format: this.format
|
||||
});
|
||||
}
|
||||
|
||||
if (options.search) {
|
||||
|
@ -185,7 +179,16 @@ File.prototype.stringify = function (format) {
|
|||
}
|
||||
|
||||
if (this.secure) {
|
||||
data = this.keys.encrypt(data);
|
||||
var self = this;
|
||||
data = Object.keys(data).reduce(function (acc, key) {
|
||||
var value = format.stringify(data[key]);
|
||||
var iv = crypto.randomBytes(16);
|
||||
var cipher = crypto.createCipheriv(self.secure.alg, self.secure.secret, iv);
|
||||
var ciphertext = cipher.update(value, 'utf8', 'hex');
|
||||
ciphertext += cipher.final('hex');
|
||||
acc[key] = { alg: self.secure.alg, value: ciphertext, iv: iv.toString('hex') };
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return format.stringify(data, null, this.spacing);
|
||||
|
@ -199,11 +202,31 @@ File.prototype.stringify = function (format) {
|
|||
File.prototype.parse = function (contents) {
|
||||
var parsed = this.format.parse(contents);
|
||||
|
||||
if (!this.secure) {
|
||||
return parsed;
|
||||
if (this.secure) {
|
||||
var self = this;
|
||||
var outdated = false;
|
||||
parsed = Object.keys(parsed).reduce(function (acc, key) {
|
||||
var value = parsed[key];
|
||||
var decipher = crypto.createDecipher(value.alg, self.secure.secret);
|
||||
if (value.iv) {
|
||||
// For backward compatibility, use createDecipheriv only if there is iv stored in file
|
||||
decipher = crypto.createDecipheriv(value.alg, self.secure.secret, Buffer.from(value.iv, 'hex'));
|
||||
} else {
|
||||
outdated = true;
|
||||
}
|
||||
var plaintext = decipher.update(value.value, 'hex', 'utf8');
|
||||
plaintext += decipher.final('utf8');
|
||||
acc[key] = self.format.parse(plaintext);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (outdated) {
|
||||
// warn user if the file is encrypted without iv
|
||||
console.warn('Your encrypted file is outdated (encrypted without iv). Please re-encrypt your file.');
|
||||
}
|
||||
}
|
||||
|
||||
return this.keys.decrypt(parsed);
|
||||
return parsed;
|
||||
|
||||
};
|
||||
|
||||
|
|
11
package-lock.json
generated
11
package-lock.json
generated
|
@ -3606,9 +3606,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"resolve": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz",
|
||||
"integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz",
|
||||
"integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==",
|
||||
"requires": {
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
|
@ -4506,11 +4506,6 @@
|
|||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"secure-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz",
|
||||
"integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
"dependencies": {
|
||||
"ini": "^1.3.0",
|
||||
"jest": "^21.2.1",
|
||||
"secure-keys": "^1.0.0",
|
||||
"yargs": "^10.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
22
test/fixtures/secure-iv.json
vendored
Normal file
22
test/fixtures/secure-iv.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"isNull": {
|
||||
"alg": "aes-256-ctr",
|
||||
"value": "16f39325",
|
||||
"iv": "49e7803a2a5ef98c7a51a8902b76dd10"
|
||||
},
|
||||
"literal": {
|
||||
"alg": "aes-256-ctr",
|
||||
"value": "647965c22605",
|
||||
"iv": "b654e01aed262f37d0acf200be193985"
|
||||
},
|
||||
"arr": {
|
||||
"alg": "aes-256-ctr",
|
||||
"value": "70c6d3b2ec3820e4d4fa3d7834fcf7fd51190a9e580944583ca7b30f42d230b7f271e72287f6c2ab4624685c779936aa2ed19b90",
|
||||
"iv": "b5263665331158c2165fe623a895870f"
|
||||
},
|
||||
"obj": {
|
||||
"alg": "aes-256-ctr",
|
||||
"value": "521f1995698896d2fe7d76d16bec9e86b9b5b25bcfae3a4f3c52ad6f0425c3fd059793b9dec7927c616f4c4c57f2b9ee833f8c865d9aa42bbb37203763703755a0c79afd13f8c0e96e9ac47f490a12105583e31ff628104e6b216265b849cdf6642eb8a4b5bd6f532339a5f5737bd6a7ae6e7bb22148cbcfa549802336cb1322fb701151b25b5863c5c6d5047875aed9806350ae0e292c259e82d5a561eb672402d20230a0c3107ff2b1e6259ccb1bbbe7a25ce965ce56cb32f679",
|
||||
"iv": "355f307363d69ed58345ae395744f5f0"
|
||||
}
|
||||
}
|
|
@ -106,14 +106,14 @@ describe('nconf/hierarchy, When using nconf', () => {
|
|||
var argv = ['--candy--something', 'foo', '--candy--something5--second', 'bar'];
|
||||
var data = '';
|
||||
process.env.candy__bonbon = 'sweet';
|
||||
var child = spawn('node', [script].concat(argv));
|
||||
var child = spawn('node', [script].concat(argv), {env: process.env});
|
||||
delete process.env.candy__bonbon;
|
||||
child.stdout.on('data', function (d) {
|
||||
data += d;
|
||||
});
|
||||
|
||||
child.on('close', function () {
|
||||
console.log(data)
|
||||
console.log(data);
|
||||
expect(JSON.parse(data)).toEqual({
|
||||
apples: true,
|
||||
candy: {
|
||||
|
|
|
@ -183,7 +183,7 @@ describe('nconf/stores/file', () => {
|
|||
describe("When using the nconf file store", () => {
|
||||
|
||||
it("the search() method when the target file exists higher in the directory tree should update the file appropriately", () => {
|
||||
var searchBase = process.env.HOME;
|
||||
var searchBase = require('os').homedir();
|
||||
var filePath = path.join(searchBase, '.nconf');
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
var store = new nconf.File({
|
||||
|
@ -206,8 +206,8 @@ describe('nconf/stores/file', () => {
|
|||
})
|
||||
describe("When using the nconf file store", () => {
|
||||
var secureStore = new nconf.File({
|
||||
file: path.join(__dirname, '..', 'fixtures', 'secure.json'),
|
||||
secure: 'super-secretzzz'
|
||||
file: path.join(__dirname, '..', 'fixtures', 'secure-iv.json'),
|
||||
secure: 'super-secret-key-32-characterszz'
|
||||
});
|
||||
|
||||
secureStore.store = data;
|
||||
|
@ -218,6 +218,7 @@ describe('nconf/stores/file', () => {
|
|||
expect(typeof contents[key]).toBe('object');
|
||||
expect(typeof contents[key].value).toBe('string');
|
||||
expect(contents[key].alg).toEqual('aes-256-ctr');
|
||||
expect(typeof contents[key].iv).toBe('string');
|
||||
});
|
||||
});
|
||||
it("the parse() method should decrypt properly", () => {
|
||||
|
@ -232,8 +233,27 @@ describe('nconf/stores/file', () => {
|
|||
});
|
||||
});
|
||||
it("the loadSync() method should decrypt properly", () => {
|
||||
var loaded = secureStore.loadSync()
|
||||
var loaded = secureStore.loadSync();
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
})
|
||||
|
||||
describe("When using the nconf file store", () => {
|
||||
var secureStore = new nconf.File({
|
||||
file: path.join(__dirname, '..', 'fixtures', 'secure.json'),
|
||||
secure: 'super-secretzzz'
|
||||
});
|
||||
|
||||
it("the load() method should decrypt legacy file properly", () => {
|
||||
secureStore.load(function (err, loaded) {
|
||||
expect(err).toBe(null);
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
});
|
||||
it("the loadSync() method should decrypt legacy file properly", () => {
|
||||
var loaded = secureStore.loadSync();
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
})
|
||||
|
||||
});
|
Loading…
Reference in a new issue