Skip to content

Commit

Permalink
[api] Allow for "secure" option to be passed to nconf.stores.File t…
Browse files Browse the repository at this point in the history
…o perform content encryption / decryption with `crypto.createCipher`.
  • Loading branch information
indexzero committed Sep 19, 2015
1 parent c2b8b97 commit 01eb094
Showing 1 changed file with 83 additions and 20 deletions.
103 changes: 83 additions & 20 deletions lib/nconf/stores/file.js
Expand Up @@ -26,11 +26,25 @@ var File = exports.File = function (options) {

Memory.call(this, options);

this.type = 'file';
this.file = options.file;
this.dir = options.dir || process.cwd();
this.format = options.format || formats.json;
this.json_spacing = options.json_spacing || 2;
this.type = 'file';
this.file = options.file;
this.dir = options.dir || process.cwd();
this.format = options.format || formats.json;
this.secure = options.secure;
this.spacing = options.json_spacing
|| options.spacing
|| 2;

if (this.secure) {
this.secure.alg = this.secure.alg || 'aes-256-ctr';
if (this.secure.secretPath) {
this.secret = fs.readFileSync(this.secure.secretPath, 'utf8');
}

if (!this.secure.secret) {
throw new Error('secure.secret option is required');
}
}

if (options.search) {
this.search(this.dir);
Expand All @@ -53,9 +67,10 @@ File.prototype.save = function (value, callback) {
value = null;
}

fs.writeFile(this.file, this.format.stringify(this.store, null, this.json_spacing), function (err) {
return err ? callback(err) : callback();
});
var contents = this.format.stringify(this.store, null, this.spacing),
bytes = this.tryEncrypt(contents);

fs.writeFile(this.file, bytes, callback);
};

//
Expand All @@ -66,12 +81,10 @@ File.prototype.save = function (value, callback) {
// using the format specified by `this.format` synchronously.
//
File.prototype.saveSync = function (value) {
try {
fs.writeFileSync(this.file, this.format.stringify(this.store, null, this.json_spacing));
}
catch (ex) {
throw(ex);
}
var contents = this.format.stringify(this.store, null, this.spacing),
bytes = this.tryEncrypt(contents);

fs.writeFileSync(this.file, bytes);
return this.store;
};

Expand All @@ -97,12 +110,14 @@ File.prototype.load = function (callback) {
}

try {
//deals with string that include BOM
// Deals with string that include BOM
var stringData = data.toString();
if (stringData.charAt(0) === '\uFEFF') {
stringData = stringData.substr(1);
}

if (stringData.charAt(0) === '\uFEFF') stringData = stringData.substr(1);
self.store = self.format.parse(stringData);

var contents = self.tryDecrypt(stringData);
self.store = self.format.parse(contents);
}
catch (ex) {
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message));
Expand Down Expand Up @@ -130,9 +145,11 @@ File.prototype.loadSync = function () {
// Else, the path exists, read it from disk
//
try {
//deals with file that include BOM
// Deals with file that include BOM
var fileData = fs.readFileSync(this.file, 'utf8');
if (fileData.charAt(0) === '\uFEFF') fileData = fileData.substr(1);
if (fileData.charAt(0) === '\uFEFF') {
fileData = fileData.substr(1);
}

data = this.format.parse(fileData);
this.store = data;
Expand All @@ -145,6 +162,40 @@ File.prototype.loadSync = function () {
return data;
};

//
// ### function tryEncrypt ()
// Returns an encrypted version of the contents IIF
// `this.secure` is enabled
//
File.prototype.tryEncrypt = function (contents) {
if (!this.secure) { return contents; }

//
// Contents have already been stringified by the format
// so no need to re-stringify here.
//
return cipherConvert(contents, {
alg: this.secure.alg,
secret: this.secure.secret,
encs: { input: 'utf8', output: 'hex' }
});
};

//
// ### function tryDecrypt (contents)
// Returns a decrypted version of the contents IFF
// `this.secure` is enabled.
//
File.prototype.tryDecrypt = function (contents) {
if (!this.secure) { return contents; }

return cipherConvert(contents, {
alg: this.secure.alg,
secret: this.secure.secret,
encs: { input: 'hex', output: 'utf8' }
});
};

//
// ### function search (base)
// #### @base {string} Base directory (or file) to begin searching for the target file.
Expand Down Expand Up @@ -235,3 +286,15 @@ File.prototype.search = function (base) {

return fullpath;
};

//
// ### function cipherConvert (contents, opts)
// Returns the result of the cipher operation
// on the contents contents.
//
function cipherConvert(contents, opts) {
var encs = opts.encs;
var cipher = crypto.createCipher(opts.alg, opts.secret);
return cipher.update(contents, encs.input, encs.output)
+ cipher.final(encs.output);
}

0 comments on commit 01eb094

Please sign in to comment.