[api] Allow for "secure" option to be passed to nconf.stores.File to perform content encryption / decryption with crypto.createCipher.

This commit is contained in:
indexzero 2015-09-18 20:40:53 -07:00
parent 4c07028e40
commit 2de2bc0b66

View file

@ -30,7 +30,21 @@ var File = exports.File = function (options) {
this.file = options.file; this.file = options.file;
this.dir = options.dir || process.cwd(); this.dir = options.dir || process.cwd();
this.format = options.format || formats.json; this.format = options.format || formats.json;
this.json_spacing = options.json_spacing || 2; 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) { if (options.search) {
this.search(this.dir); this.search(this.dir);
@ -53,9 +67,10 @@ File.prototype.save = function (value, callback) {
value = null; value = null;
} }
fs.writeFile(this.file, this.format.stringify(this.store, null, this.json_spacing), function (err) { var contents = this.format.stringify(this.store, null, this.spacing),
return err ? callback(err) : callback(); bytes = this.tryEncrypt(contents);
});
fs.writeFile(this.file, bytes, callback);
}; };
// //
@ -66,12 +81,10 @@ File.prototype.save = function (value, callback) {
// using the format specified by `this.format` synchronously. // using the format specified by `this.format` synchronously.
// //
File.prototype.saveSync = function (value) { File.prototype.saveSync = function (value) {
try { var contents = this.format.stringify(this.store, null, this.spacing),
fs.writeFileSync(this.file, this.format.stringify(this.store, null, this.json_spacing)); bytes = this.tryEncrypt(contents);
}
catch (ex) { fs.writeFileSync(this.file, bytes);
throw(ex);
}
return this.store; return this.store;
}; };
@ -97,12 +110,14 @@ File.prototype.load = function (callback) {
} }
try { try {
//deals with string that include BOM // Deals with string that include BOM
var stringData = data.toString(); var stringData = data.toString();
if (stringData.charAt(0) === '\uFEFF') {
stringData = stringData.substr(1);
}
if (stringData.charAt(0) === '\uFEFF') stringData = stringData.substr(1); var contents = self.tryDecrypt(stringData);
self.store = self.format.parse(stringData); self.store = self.format.parse(contents);
} }
catch (ex) { catch (ex) {
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message)); return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message));
@ -130,9 +145,11 @@ File.prototype.loadSync = function () {
// Else, the path exists, read it from disk // Else, the path exists, read it from disk
// //
try { try {
//deals with file that include BOM // Deals with file that include BOM
var fileData = fs.readFileSync(this.file, 'utf8'); 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); data = this.format.parse(fileData);
this.store = data; this.store = data;
@ -145,6 +162,40 @@ File.prototype.loadSync = function () {
return data; 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) // ### function search (base)
// #### @base {string} Base directory (or file) to begin searching for the target file. // #### @base {string} Base directory (or file) to begin searching for the target file.
@ -235,3 +286,15 @@ File.prototype.search = function (base) {
return fullpath; 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);
}