From ebb50ae344f96c589b3a6dffb664ba6a82b2d42f Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Thu, 10 Aug 2017 14:30:04 +0530 Subject: [PATCH 1/9] Working on SEARCH-116 Implement Encryption and Decryption of Index --- js/cryptoLib/index.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 js/cryptoLib/index.js diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js new file mode 100644 index 00000000..e25ca53c --- /dev/null +++ b/js/cryptoLib/index.js @@ -0,0 +1,25 @@ +'use strict'; +const electron = require('electron'); +const app = electron.app; +const path = require('path'); +const isDevEnv = require('../utils/misc.js').isDevEnv; + +const userData = path.join(app.getPath('userData')); +const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data'); + +class Crypto { + + constructor() { + this.indexDataFolder = INDEX_DATA_FOLDER; + } + + encryption() { + console.log(this.indexDataFolder) + } + + decryption() { + console.log(this.indexDataFolder) + } +} + +module.exports = Crypto; \ No newline at end of file From f6ae42f854b971a634b320f92817c37e20c723d9 Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Mon, 14 Aug 2017 10:47:01 +0530 Subject: [PATCH 2/9] SEARCH-116 - Temporary push --- js/cryptoLib/index.js | 98 +++++++++++++++++++++++++++++++++++++++++-- js/main.js | 26 ++++++++++++ package.json | 4 +- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index e25ca53c..0a1f5e7e 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -2,23 +2,115 @@ const electron = require('electron'); const app = electron.app; const path = require('path'); +const fs = require('fs'); +const crypto = require('crypto'); +const archiver = require('archiver'); +const zipArchive = archiver('zip'); +const extract = require('extract-zip'); const isDevEnv = require('../utils/misc.js').isDevEnv; const userData = path.join(app.getPath('userData')); -const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data'); +const INDEX_DATA_FOLDER = isDevEnv ? './msgsjson' : path.join(userData, 'data'); class Crypto { constructor() { this.indexDataFolder = INDEX_DATA_FOLDER; + this.decipher = crypto.createDecipher('aes256', 'temp'); + this.cipher = crypto.createCipher('aes256', "temp"); + this.dump = path.join(__dirname, '..', '..'); + this.encryptedIndex = 'encryptedIndex.enc'; + this.zipErrored = false; } encryption() { - console.log(this.indexDataFolder) + let self = this; + return new Promise(function (resolve, reject) { + + let output = fs.createWriteStream(`${self.dump}/content.zip`); + + output.on('close', function () { + + const input = fs.createReadStream(`${self.dump}/content.zip`); + const outPutEncryption = fs.createWriteStream(self.encryptedIndex); + + input.pipe(self.cipher).pipe(outPutEncryption).on('finish', function (err, res) { + if (err) { + reject(new Error(err)); + } + if (!self.zipErrored) { + fs.unlinkSync(`${self.dump}/content.zip`); + Crypto.deleteFolderRecursive(self.indexDataFolder) + .then(function () { + resolve(res); + }) + .catch(function (error) { + console.log(error) + }); + } + }); + }); + + zipArchive.pipe(output); + + zipArchive.directory(self.indexDataFolder, true); + + zipArchive.finalize(function (err) { + if (err) { + self.zipErrored = true; + reject(new Error(err)); + } + }); + }); } decryption() { - console.log(this.indexDataFolder) + let self = this; + return new Promise(function (resolve, reject) { + + const input = fs.createReadStream(self.encryptedIndex); + const output = fs.createWriteStream(`${self.dump}/decrypted.zip`); + + function unzip() { + let temp = path.join(__dirname, '..', '..'); + extract(`${self.dump}/decrypted.zip`, {dir: temp}, function (err) { + if (err) reject(err); + fs.unlink(`${self.dump}/decrypted.zip`, function () { + resolve('success') + }); + }) + } + + input.pipe(self.decipher).pipe(output).on('finish', function () { + var readStream = fs.createReadStream(`${self.dump}/decrypted.zip`); + readStream + .on('data', function (data) { + if (!data) reject("error reading zip"); + unzip(); + }) + .on('error', function (error) { + console.log('Error:', error.message); + }); + }); + }); + } + + static deleteFolderRecursive(pt) { + return new Promise(function (resolve, reject) { + if (fs.existsSync(pt)) { + fs.readdirSync(pt).forEach(function (file) { + var curPath = pt + "/" + file; + if (fs.lstatSync(curPath).isDirectory()) { + Crypto.deleteFolderRecursive(curPath); + } else { + fs.unlinkSync(curPath); + } + }); + resolve(fs.rmdirSync(pt)); + } else { + reject('no file'); + } + }); } } diff --git a/js/main.js b/js/main.js index 34704be5..2a0df7a5 100644 --- a/js/main.js +++ b/js/main.js @@ -15,6 +15,8 @@ const path = require('path'); const AppDirectory = require('appdirectory'); const dirs = new AppDirectory('Symphony'); +const Crypto = require('./cryptoLib/index'); + require('electron-dl')(); // used to check if a url was opened when the app was already open @@ -56,6 +58,16 @@ var symphonyAutoLauncher = new AutoLaunch({ path: process.execPath, }); +let crypto = new Crypto(); + +crypto.decryption() + .then(function () { + console.log('success') + }) + .catch(function (err) { + console.log(err) + }); + /** * This method will be called when Electron has finished * initialization and is ready to create browser windows. @@ -75,6 +87,20 @@ app.on('activate', function () { } }); +app.on('will-quit', function (e) { + e.preventDefault(); + crypto.encryption() + .then(function (err, res) { + if (err) { + throw new Error(err); + } + app.exit(); + }) + .catch(function (err) { + electron.dialog.showErrorBox('error', err); + }); +}); + // adds 'symphony' as a protocol // in the system. plist file in macOS // and registry keys in windows diff --git a/package.json b/package.json index f0c30cd8..8ec04720 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,9 @@ "electron-squirrel-startup": "^1.0.0", "filesize": "^3.5.10", "keymirror": "0.1.1", - "winreg": "^1.2.3" + "winreg": "^1.2.3", + "archiver": "latest", + "extract-zip": "latest" }, "optionalDependencies": { "screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1" From eff466d1995ca3e00ec927e44c3d9020b6ea77bb Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Fri, 18 Aug 2017 12:56:48 +0530 Subject: [PATCH 3/9] SEARCH-116 This is using CTR mode --- js/cryptoLib/index.js | 83 ++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index 0a1f5e7e..44f8be31 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -11,41 +11,41 @@ const isDevEnv = require('../utils/misc.js').isDevEnv; const userData = path.join(app.getPath('userData')); const INDEX_DATA_FOLDER = isDevEnv ? './msgsjson' : path.join(userData, 'data'); +const MODE = 'aes-256-ctr'; class Crypto { constructor() { this.indexDataFolder = INDEX_DATA_FOLDER; - this.decipher = crypto.createDecipher('aes256', 'temp'); - this.cipher = crypto.createCipher('aes256', "temp"); + this.key = '53796d70686f6e792074657374206b657920666f7220656e6372797074696f6e20'; this.dump = path.join(__dirname, '..', '..'); this.encryptedIndex = 'encryptedIndex.enc'; this.zipErrored = false; } encryption() { - let self = this; - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { - let output = fs.createWriteStream(`${self.dump}/content.zip`); + let output = fs.createWriteStream(`${this.dump}/content.zip`); - output.on('close', function () { + output.on('close', () => { - const input = fs.createReadStream(`${self.dump}/content.zip`); - const outPutEncryption = fs.createWriteStream(self.encryptedIndex); + const input = fs.createReadStream(`${this.dump}/content.zip`); + const outputEncryption = fs.createWriteStream(this.encryptedIndex); + const cipher = crypto.createCipher(MODE, this.key); - input.pipe(self.cipher).pipe(outPutEncryption).on('finish', function (err, res) { + input.pipe(cipher).pipe(outputEncryption).on('finish', (err, res) => { if (err) { reject(new Error(err)); } - if (!self.zipErrored) { - fs.unlinkSync(`${self.dump}/content.zip`); - Crypto.deleteFolderRecursive(self.indexDataFolder) + if (!this.zipErrored) { + fs.unlinkSync(`${this.dump}/content.zip`); + Crypto.deleteFolderRecursive(this.indexDataFolder) .then(function () { resolve(res); }) .catch(function (error) { - console.log(error) + reject(new Error(error)) }); } }); @@ -53,11 +53,11 @@ class Crypto { zipArchive.pipe(output); - zipArchive.directory(self.indexDataFolder, true); + zipArchive.directory(this.indexDataFolder, true); - zipArchive.finalize(function (err) { + zipArchive.finalize((err) => { if (err) { - self.zipErrored = true; + this.zipErrored = true; reject(new Error(err)); } }); @@ -65,41 +65,44 @@ class Crypto { } decryption() { - let self = this; - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { + const input = fs.createReadStream(this.encryptedIndex); + const output = fs.createWriteStream(`${this.dump}/decrypted.zip`); + const deCipher = crypto.createDecipher(MODE, this.key); - const input = fs.createReadStream(self.encryptedIndex); - const output = fs.createWriteStream(`${self.dump}/decrypted.zip`); + input.pipe(deCipher).pipe(output).on('finish', () => { + let readStream = fs.createReadStream(`${this.dump}/decrypted.zip`); + readStream + .on('data', (data) => { + if (!data) { + reject(new Error("error reading zip")); + } + unzip(); + }) + .on('error', (error) => { + reject(new Error(error.message)); + }); + }); - function unzip() { + let unzip = () => { let temp = path.join(__dirname, '..', '..'); - extract(`${self.dump}/decrypted.zip`, {dir: temp}, function (err) { - if (err) reject(err); - fs.unlink(`${self.dump}/decrypted.zip`, function () { + extract(`${this.dump}/decrypted.zip`, {dir: temp}, (err) => { + if (err) { + reject(new Error(err)); + } + fs.unlink(`${this.dump}/decrypted.zip`, () => { resolve('success') }); }) - } - - input.pipe(self.decipher).pipe(output).on('finish', function () { - var readStream = fs.createReadStream(`${self.dump}/decrypted.zip`); - readStream - .on('data', function (data) { - if (!data) reject("error reading zip"); - unzip(); - }) - .on('error', function (error) { - console.log('Error:', error.message); - }); - }); + }; }); } static deleteFolderRecursive(pt) { - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { if (fs.existsSync(pt)) { - fs.readdirSync(pt).forEach(function (file) { - var curPath = pt + "/" + file; + fs.readdirSync(pt).forEach((file) => { + let curPath = pt + "/" + file; if (fs.lstatSync(curPath).isDirectory()) { Crypto.deleteFolderRecursive(curPath); } else { From 52d6a9e8670d37b1f67ad2e3d1b2fc80cc93d47e Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Fri, 18 Aug 2017 14:48:22 +0530 Subject: [PATCH 4/9] SEARCH-116-GCM This is using GCM mode --- js/cryptoLib/crypto.js | 249 +++++++++++++++++++++++++++++++++++++++++ js/cryptoLib/index.js | 62 ++++++---- 2 files changed, 291 insertions(+), 20 deletions(-) create mode 100644 js/cryptoLib/crypto.js diff --git a/js/cryptoLib/crypto.js b/js/cryptoLib/crypto.js new file mode 100644 index 00000000..8cde0dbc --- /dev/null +++ b/js/cryptoLib/crypto.js @@ -0,0 +1,249 @@ +/** + * AES GCM Stream + * This module exports encrypt and decrypt stream constructors which can be + * used to protect data with authenticated encryption. + * + * Helper methods are also provided to do things like generate secure keys + * and salt. + */ +'use strict'; + +let stream = require('stream'); +let Transform = stream.Transform; +let util = require('util'); +let crypto = require('crypto'); + +let PBKDF2_PASS_LENGTH = 256; +let PBKDF2_SALT_LENGTH = 32; +let PBKDF2_ITERATIONS = 5000; +let PBKDF2_DIGEST = 'sha256'; +let KEY_LENGTH = 32; // bytes +let GCM_NONCE_LENGTH = 12; //bytes +let GCM_MAC_LENGTH = 16; //bytes + +let keyEncoding = 'base64'; + +/** + * Private helper method to validate a key passed into the Encrypt and Decrypt streams. + * Strings are converted it into a buffer, buffers are returned as they are. + * @param key + * @throws Missing, Encoding, or Length errors + * @returns Buffer + */ +let validateAndConvertKey = function(key) { + if (key && key instanceof Buffer && key.length === KEY_LENGTH) { + return key; + } else if (key && typeof key === 'string') { + // Because we don't have a reliable way to test string encoding, we assume that a string type + // key was created by the createEncodedKey method and that it is using the keyEncoding which + // has been set on the module, whether that's the default or something explicitly set by the + // user via the setKeyEncoding method. + let bufKey = new Buffer(key, keyEncoding); + if (bufKey.length !== KEY_LENGTH) { + let encodingErrorMessage = 'Provided key string is either of an unknown encoding (expected: ' + + keyEncoding + ') or the wrong length.'; + throw new Error(encodingErrorMessage); + } + return bufKey; + } else { + let message = 'The key options property is required! Expected ' + + keyEncoding + ' encoded string or a buffer.'; + throw new Error(message); + } +}; + +exports.encrypt = EncryptionStream; +exports.decrypt = DecryptionStream; + +/** + * getEncoding + * Helper which returns the current encoding being used for keys + * @returns {string} + */ +exports.getKeyEncoding = function() { + return keyEncoding; +}; + +/** + * setEncoding + * Helper to set the encoding being used for keys + * @param enc + */ +exports.setKeyEncoding = function(enc) { + keyEncoding = Buffer.isEncoding(enc) ? enc : keyEncoding; +}; + +/** + * createSalt + * Helper method that returns a salt + * @returns string + * @throws error + */ +exports.createSalt = function(length) { + try { + return crypto.randomBytes(length); + } catch (ex) { + console.error('Problem reading random data and generating salt!'); + throw ex; + } +}; + +/** + * createKeyBuffer + * Method which returns a buffer representing a secure key generated with PBKDF2 + * @returns Buffer + */ +exports.createKeyBuffer = function() { + try { + let passphrase = crypto.randomBytes(PBKDF2_PASS_LENGTH); + let salt = this.createSalt(PBKDF2_SALT_LENGTH); + return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST); + } catch (ex) { + console.error('Problem reading random data and generating a key!'); + throw ex; + } +}; + +/** + * createEncodedKey + * Helper method that returns an encoded key + * @returns string + * @throws error + */ +exports.createEncodedKey = function() { + return exports.createKeyBuffer().toString(keyEncoding); +}; + +/** + * EncryptionStream + * A constructor which returns an encryption stream + * The stream first outputs a 12 byte nonce then encrypted cipher text. + * When the stream is flushed it outputs a 16 byte MAC. + * @param options Object Object.key is the only required param + * @returns {EncryptionStream} + * @constructor + */ +function EncryptionStream(options) { + if (!(this instanceof EncryptionStream)) { + return new EncryptionStream(options); + } + + let nonce = options.nonce || exports.createSalt(12); + + this._key = validateAndConvertKey(options.key); + this._cipher = crypto.createCipheriv('aes-256-gcm', this._key, nonce); + + Transform.call(this, options); + this.push(nonce); +} +util.inherits(EncryptionStream, Transform); + +EncryptionStream.prototype._transform = function(chunk, enc, cb) { + this.push(this._cipher.update(chunk)); + cb(); +}; + +EncryptionStream.prototype._flush = function(cb) { + // final must be called on the cipher before generating a MAC + this._cipher.final(); // this will never output data + this.push(this._cipher.getAuthTag()); // 16 bytes + + cb(); +}; + + +/** + * DecryptionStream + * A constructor which returns a decryption stream + * The stream assumes the first 12 bytes of data are the nonce and the final + * 16 bytes received is the MAC. + * @param options Object Object.key is the only required param + * @returns {DecryptionStream} + * @constructor + */ +function DecryptionStream(options) { + if (!(this instanceof DecryptionStream)) { + return new DecryptionStream(options); + } + + this._started = false; + this._nonce = new Buffer(GCM_NONCE_LENGTH); + this._nonceBytesRead = 0; + this._cipherTextChunks = []; + this._key = validateAndConvertKey(options.key); + + Transform.call(this, options); +} +util.inherits(DecryptionStream, Transform); + +DecryptionStream.prototype._transform = function(chunk, enc, cb) { + let chunkLength = chunk.length; + let chunkOffset = 0; + if (!this._started) { + if (this._nonceBytesRead < GCM_NONCE_LENGTH) { + let nonceRemaining = GCM_NONCE_LENGTH - this._nonceBytesRead; + chunkOffset = chunkLength <= nonceRemaining ? chunkLength : nonceRemaining; + chunk.copy(this._nonce, this._nonceBytesRead, 0, chunkOffset); + chunk = chunk.slice(chunkOffset); + chunkLength = chunk.length; + this._nonceBytesRead += chunkOffset; + } + + + if (this._nonceBytesRead === GCM_NONCE_LENGTH) { + this._decipher = crypto.createDecipheriv('aes-256-gcm', this._key, this._nonce); + this._started = true; + } + } + + // We can't use an else because we have no idea how long our chunks will be + // all we know is that once we've got a nonce we can start storing cipher text + if (this._started) { + this._cipherTextChunks.push(chunk); + } + + cb(); +}; + +DecryptionStream.prototype._flush = function(cb) { + let mac = pullOutMac(this._cipherTextChunks); + if (!mac) { + return this.emit('error', new Error('Decryption failed: bad cipher text.')); + } + this._decipher.setAuthTag(mac); + let decrypted = this._cipherTextChunks.map(function(item) { + return this._decipher.update(item); + }, this); + try { + this._decipher.final(); + } catch (e) { + return cb(e); + } + decrypted.forEach(function(item) { + this.push(item); + }, this); + cb(); +}; + +function pullOutMac(array) { + let macBits = []; + let macByteCount = 0; + let current, macStartIndex; + while (macByteCount !== GCM_MAC_LENGTH && array.length) { + current = array.pop(); + if (macByteCount + current.length <= GCM_MAC_LENGTH) { + macBits.push(current); + macByteCount += current.length; + } else { + macStartIndex = (macByteCount + current.length) - GCM_MAC_LENGTH; + macBits.push(current.slice(macStartIndex)); + array.push(current.slice(0, macStartIndex)); + macByteCount += (current.length - macStartIndex); + } + } + if (macByteCount !== GCM_MAC_LENGTH) { + return; + } + macBits.reverse(); + return Buffer.concat(macBits, GCM_MAC_LENGTH); +} \ No newline at end of file diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index 44f8be31..f27f22c1 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -3,26 +3,31 @@ const electron = require('electron'); const app = electron.app; const path = require('path'); const fs = require('fs'); -const crypto = require('crypto'); const archiver = require('archiver'); const zipArchive = archiver('zip'); const extract = require('extract-zip'); const isDevEnv = require('../utils/misc.js').isDevEnv; +const crypto = require('./crypto'); const userData = path.join(app.getPath('userData')); -const INDEX_DATA_FOLDER = isDevEnv ? './msgsjson' : path.join(userData, 'data'); -const MODE = 'aes-256-ctr'; +const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data'); +const TEMPORARY_PATH = isDevEnv ? path.join(__dirname, '..', '..') : userData; class Crypto { constructor() { this.indexDataFolder = INDEX_DATA_FOLDER; - this.key = '53796d70686f6e792074657374206b657920666f7220656e6372797074696f6e20'; - this.dump = path.join(__dirname, '..', '..'); + this.dump = TEMPORARY_PATH; + this.key = "XrwVgWR4czB1a9scwvgRUNbXiN3W0oWq7oUBenyq7bo="; // temporary only this.encryptedIndex = 'encryptedIndex.enc'; this.zipErrored = false; } + /** + * Creates a zip of the data folder and encrypting + * removing the data folder and the dump files + * @returns {Promise} + */ encryption() { return new Promise((resolve, reject) => { @@ -32,9 +37,12 @@ class Crypto { const input = fs.createReadStream(`${this.dump}/content.zip`); const outputEncryption = fs.createWriteStream(this.encryptedIndex); - const cipher = crypto.createCipher(MODE, this.key); + let config = { + key: this.key + }; + const encrypt = crypto.encrypt(config); - input.pipe(cipher).pipe(outputEncryption).on('finish', (err, res) => { + input.pipe(encrypt).pipe(outputEncryption).on('finish', (err, res) => { if (err) { reject(new Error(err)); } @@ -64,52 +72,66 @@ class Crypto { }); } + /** + * Decrypting the .enc file and unzipping + * removing the .enc file and the dump files + * @returns {Promise} + */ decryption() { return new Promise((resolve, reject) => { const input = fs.createReadStream(this.encryptedIndex); const output = fs.createWriteStream(`${this.dump}/decrypted.zip`); - const deCipher = crypto.createDecipher(MODE, this.key); + let config = { + key: this.key + }; + const decrypt = crypto.decrypt(config); - input.pipe(deCipher).pipe(output).on('finish', () => { + input.pipe(decrypt).pipe(output).on('finish', () => { let readStream = fs.createReadStream(`${this.dump}/decrypted.zip`); readStream .on('data', (data) => { if (!data) { reject(new Error("error reading zip")); } - unzip(); + zip(); }) .on('error', (error) => { reject(new Error(error.message)); }); }); - let unzip = () => { - let temp = path.join(__dirname, '..', '..'); - extract(`${this.dump}/decrypted.zip`, {dir: temp}, (err) => { + let zip = () => { + extract(`${this.dump}/decrypted.zip`, {dir: TEMPORARY_PATH}, (err) => { if (err) { reject(new Error(err)); } fs.unlink(`${this.dump}/decrypted.zip`, () => { - resolve('success') + fs.unlink(this.encryptedIndex, () => { + resolve('success'); + }) }); }) - }; + } }); } - static deleteFolderRecursive(pt) { + /** + * Removing all the folders and files inside the data folder + * @param {String} location + * @returns {Promise} + */ + static deleteFolderRecursive(location) { return new Promise((resolve, reject) => { - if (fs.existsSync(pt)) { - fs.readdirSync(pt).forEach((file) => { - let curPath = pt + "/" + file; + if (fs.existsSync(location)) { + fs.readdirSync(location).forEach((file) => { + let curPath = location + "/" + file; if (fs.lstatSync(curPath).isDirectory()) { Crypto.deleteFolderRecursive(curPath); } else { fs.unlinkSync(curPath); } }); - resolve(fs.rmdirSync(pt)); + resolve(fs.rmdirSync(location)); } else { reject('no file'); } From 4053d22dfc7f6ef8da5d95c994b5dd094f542437 Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Fri, 18 Aug 2017 17:28:30 +0530 Subject: [PATCH 5/9] SEARCH-116-GCM Changed the path from data to user folder --- js/cryptoLib/index.js | 46 +++++++++++++++++++++++++++++++++++++------ js/main.js | 35 ++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index f27f22c1..d4257287 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -10,13 +10,20 @@ const isDevEnv = require('../utils/misc.js').isDevEnv; const crypto = require('./crypto'); const userData = path.join(app.getPath('userData')); -const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data'); +const INDEX_DATA_FOLDER = isDevEnv ? './data/search_index' : path.join(userData, 'data/search_index'); const TEMPORARY_PATH = isDevEnv ? path.join(__dirname, '..', '..') : userData; class Crypto { + // TODO: Need to pass key for encrypting and decrypting constructor() { - this.indexDataFolder = INDEX_DATA_FOLDER; + + // will be handling after implementing in client app + let userId = 'user_data'; + let INDEX_VERSION = 'v1'; + // will be handling after implementing in client app + + this.indexDataFolder = INDEX_DATA_FOLDER + '_' + userId + '_' + INDEX_VERSION; this.dump = TEMPORARY_PATH; this.key = "XrwVgWR4czB1a9scwvgRUNbXiN3W0oWq7oUBenyq7bo="; // temporary only this.encryptedIndex = 'encryptedIndex.enc'; @@ -31,9 +38,22 @@ class Crypto { encryption() { return new Promise((resolve, reject) => { + if (!fs.existsSync(this.indexDataFolder)){ + // will be handling after implementing in client app + reject(); + return; + } + let output = fs.createWriteStream(`${this.dump}/content.zip`); - output.on('close', () => { + + zipArchive.on('end', () => { + + if (!fs.existsSync(`${this.dump}/content.zip`)){ + // will be handling after implementing in client app + reject(); + return; + } const input = fs.createReadStream(`${this.dump}/content.zip`); const outputEncryption = fs.createWriteStream(this.encryptedIndex); @@ -61,7 +81,7 @@ class Crypto { zipArchive.pipe(output); - zipArchive.directory(this.indexDataFolder, true); + zipArchive.directory(this.indexDataFolder); zipArchive.finalize((err) => { if (err) { @@ -79,6 +99,13 @@ class Crypto { */ decryption() { return new Promise((resolve, reject) => { + + if (!fs.existsSync(this.encryptedIndex)){ + // will be handling after implementing in client app + reject(); + return; + } + const input = fs.createReadStream(this.encryptedIndex); const output = fs.createWriteStream(`${this.dump}/decrypted.zip`); let config = { @@ -87,20 +114,27 @@ class Crypto { const decrypt = crypto.decrypt(config); input.pipe(decrypt).pipe(output).on('finish', () => { + + if (!fs.existsSync(`${this.dump}/decrypted.zip`)){ + // will be handling after implementing in client app + reject(); + return; + } + let readStream = fs.createReadStream(`${this.dump}/decrypted.zip`); readStream .on('data', (data) => { if (!data) { reject(new Error("error reading zip")); } - zip(); + extractZip(); }) .on('error', (error) => { reject(new Error(error.message)); }); }); - let zip = () => { + let extractZip = () => { extract(`${this.dump}/decrypted.zip`, {dir: TEMPORARY_PATH}, (err) => { if (err) { reject(new Error(err)); diff --git a/js/main.js b/js/main.js index 2a0df7a5..b6761835 100644 --- a/js/main.js +++ b/js/main.js @@ -14,11 +14,12 @@ const childProcess = require('child_process'); const path = require('path'); const AppDirectory = require('appdirectory'); const dirs = new AppDirectory('Symphony'); - -const Crypto = require('./cryptoLib/index'); +const Crypto = require('./cryptoLib'); +const crypto = new Crypto(); require('electron-dl')(); + // used to check if a url was opened when the app was already open let isAppAlreadyOpen = false; @@ -58,14 +59,17 @@ var symphonyAutoLauncher = new AutoLaunch({ path: process.execPath, }); -let crypto = new Crypto(); - +/** + * This is for demo purpose only + * will be removing this after implementing + * in the client-app + */ crypto.decryption() .then(function () { - console.log('success') + // will be handling after implementing client app }) - .catch(function (err) { - console.log(err) + .catch(function () { + // will be handling after implementing client app }); /** @@ -89,15 +93,20 @@ app.on('activate', function () { app.on('will-quit', function (e) { e.preventDefault(); + + /** + * This is for demo purpose only + * will be removing this after implementing + * in client-app + */ crypto.encryption() - .then(function (err, res) { - if (err) { - throw new Error(err); - } + .then(function () { + // will be handling after implementing in client app app.exit(); }) - .catch(function (err) { - electron.dialog.showErrorBox('error', err); + .catch(function () { + // will be handling after implementing client app + app.exit(); }); }); From 5e738a3eedaf844f0d408d39445b48f6d562ade7 Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Fri, 18 Aug 2017 17:29:23 +0530 Subject: [PATCH 6/9] SEARCH-116-GCM Changed the path from data to user folder --- js/cryptoLib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index d4257287..08c880eb 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -15,7 +15,7 @@ const TEMPORARY_PATH = isDevEnv ? path.join(__dirname, '..', '..') : userData; class Crypto { - // TODO: Need to pass key for encrypting and decrypting + // TODO: Need to pass key for encryption and decryption constructor() { // will be handling after implementing in client app From e01e334fa9153b3aaf5bf2636f90e7de40118b8e Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Mon, 21 Aug 2017 13:39:07 +0530 Subject: [PATCH 7/9] SEARCH-116-GCM 1. Removed the key creation in crypto 2. Hash SHA256 3. Changed the encrypted zip name --- js/cryptoLib/crypto.js | 89 +++++++++++------------------------------- js/cryptoLib/index.js | 9 +++-- package.json | 7 ++-- 3 files changed, 32 insertions(+), 73 deletions(-) diff --git a/js/cryptoLib/crypto.js b/js/cryptoLib/crypto.js index 8cde0dbc..f7d9feb3 100644 --- a/js/cryptoLib/crypto.js +++ b/js/cryptoLib/crypto.js @@ -2,9 +2,6 @@ * AES GCM Stream * This module exports encrypt and decrypt stream constructors which can be * used to protect data with authenticated encryption. - * - * Helper methods are also provided to do things like generate secure keys - * and salt. */ 'use strict'; @@ -12,11 +9,8 @@ let stream = require('stream'); let Transform = stream.Transform; let util = require('util'); let crypto = require('crypto'); +let forge = require('node-forge'); -let PBKDF2_PASS_LENGTH = 256; -let PBKDF2_SALT_LENGTH = 32; -let PBKDF2_ITERATIONS = 5000; -let PBKDF2_DIGEST = 'sha256'; let KEY_LENGTH = 32; // bytes let GCM_NONCE_LENGTH = 12; //bytes let GCM_MAC_LENGTH = 16; //bytes @@ -34,44 +28,33 @@ let validateAndConvertKey = function(key) { if (key && key instanceof Buffer && key.length === KEY_LENGTH) { return key; } else if (key && typeof key === 'string') { - // Because we don't have a reliable way to test string encoding, we assume that a string type - // key was created by the createEncodedKey method and that it is using the keyEncoding which - // has been set on the module, whether that's the default or something explicitly set by the - // user via the setKeyEncoding method. - let bufKey = new Buffer(key, keyEncoding); + let md = forge.md.sha256.create(); + md.update(key); + let bufKey = new Buffer(bits2b64(md.digest().getBytes()), keyEncoding); if (bufKey.length !== KEY_LENGTH) { let encodingErrorMessage = 'Provided key string is either of an unknown encoding (expected: ' + keyEncoding + ') or the wrong length.'; throw new Error(encodingErrorMessage); } return bufKey; - } else { - let message = 'The key options property is required! Expected ' + - keyEncoding + ' encoded string or a buffer.'; - throw new Error(message); } + let message = 'The key options property is required! Expected ' + + keyEncoding + ' encoded string or a buffer.'; + throw new Error(message); +}; + +/** + * encode bits to b64 + * @param bits + * @returns {*} + */ +let bits2b64 = function (bits) { + return forge.util.encode64(bits); }; exports.encrypt = EncryptionStream; exports.decrypt = DecryptionStream; -/** - * getEncoding - * Helper which returns the current encoding being used for keys - * @returns {string} - */ -exports.getKeyEncoding = function() { - return keyEncoding; -}; - -/** - * setEncoding - * Helper to set the encoding being used for keys - * @param enc - */ -exports.setKeyEncoding = function(enc) { - keyEncoding = Buffer.isEncoding(enc) ? enc : keyEncoding; -}; /** * createSalt @@ -88,32 +71,6 @@ exports.createSalt = function(length) { } }; -/** - * createKeyBuffer - * Method which returns a buffer representing a secure key generated with PBKDF2 - * @returns Buffer - */ -exports.createKeyBuffer = function() { - try { - let passphrase = crypto.randomBytes(PBKDF2_PASS_LENGTH); - let salt = this.createSalt(PBKDF2_SALT_LENGTH); - return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST); - } catch (ex) { - console.error('Problem reading random data and generating a key!'); - throw ex; - } -}; - -/** - * createEncodedKey - * Helper method that returns an encoded key - * @returns string - * @throws error - */ -exports.createEncodedKey = function() { - return exports.createKeyBuffer().toString(keyEncoding); -}; - /** * EncryptionStream * A constructor which returns an encryption stream @@ -151,7 +108,6 @@ EncryptionStream.prototype._flush = function(cb) { cb(); }; - /** * DecryptionStream * A constructor which returns a decryption stream @@ -179,13 +135,14 @@ util.inherits(DecryptionStream, Transform); DecryptionStream.prototype._transform = function(chunk, enc, cb) { let chunkLength = chunk.length; let chunkOffset = 0; + let _chunk = chunk; if (!this._started) { if (this._nonceBytesRead < GCM_NONCE_LENGTH) { let nonceRemaining = GCM_NONCE_LENGTH - this._nonceBytesRead; chunkOffset = chunkLength <= nonceRemaining ? chunkLength : nonceRemaining; - chunk.copy(this._nonce, this._nonceBytesRead, 0, chunkOffset); - chunk = chunk.slice(chunkOffset); - chunkLength = chunk.length; + _chunk.copy(this._nonce, this._nonceBytesRead, 0, chunkOffset); + _chunk = _chunk.slice(chunkOffset); + chunkLength = _chunk.length; this._nonceBytesRead += chunkOffset; } @@ -199,7 +156,7 @@ DecryptionStream.prototype._transform = function(chunk, enc, cb) { // We can't use an else because we have no idea how long our chunks will be // all we know is that once we've got a nonce we can start storing cipher text if (this._started) { - this._cipherTextChunks.push(chunk); + this._cipherTextChunks.push(_chunk); } cb(); @@ -222,7 +179,7 @@ DecryptionStream.prototype._flush = function(cb) { decrypted.forEach(function(item) { this.push(item); }, this); - cb(); + return cb(); }; function pullOutMac(array) { @@ -242,7 +199,7 @@ function pullOutMac(array) { } } if (macByteCount !== GCM_MAC_LENGTH) { - return; + return null; } macBits.reverse(); return Buffer.concat(macBits, GCM_MAC_LENGTH); diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index 08c880eb..1006274f 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -24,6 +24,7 @@ class Crypto { // will be handling after implementing in client app this.indexDataFolder = INDEX_DATA_FOLDER + '_' + userId + '_' + INDEX_VERSION; + this.permanentIndexFolderName = 'search_index_' + userId + '_' + INDEX_VERSION; this.dump = TEMPORARY_PATH; this.key = "XrwVgWR4czB1a9scwvgRUNbXiN3W0oWq7oUBenyq7bo="; // temporary only this.encryptedIndex = 'encryptedIndex.enc'; @@ -44,18 +45,18 @@ class Crypto { return; } - let output = fs.createWriteStream(`${this.dump}/content.zip`); + let output = fs.createWriteStream(`${this.dump}/${this.permanentIndexFolderName}.zip`); zipArchive.on('end', () => { - if (!fs.existsSync(`${this.dump}/content.zip`)){ + if (!fs.existsSync(`${this.dump}/${this.permanentIndexFolderName}.zip`)){ // will be handling after implementing in client app reject(); return; } - const input = fs.createReadStream(`${this.dump}/content.zip`); + const input = fs.createReadStream(`${this.dump}/${this.permanentIndexFolderName}.zip`); const outputEncryption = fs.createWriteStream(this.encryptedIndex); let config = { key: this.key @@ -67,7 +68,7 @@ class Crypto { reject(new Error(err)); } if (!this.zipErrored) { - fs.unlinkSync(`${this.dump}/content.zip`); + fs.unlinkSync(`${this.dump}/${this.permanentIndexFolderName}.zip`); Crypto.deleteFolderRecursive(this.indexDataFolder) .then(function () { resolve(res); diff --git a/package.json b/package.json index 8ec04720..d389597f 100644 --- a/package.json +++ b/package.json @@ -89,17 +89,18 @@ "dependencies": { "@paulcbetts/system-idle-time": "^1.0.4", "appdirectory": "^0.1.0", + "archiver": "^2.0.0", "async.map": "^0.5.2", "async.mapseries": "^0.5.2", "auto-launch": "^5.0.1", "electron-dl": "^1.9.0", "electron-spellchecker": "^1.2.0", "electron-squirrel-startup": "^1.0.0", + "extract-zip": "^1.6.5", "filesize": "^3.5.10", "keymirror": "0.1.1", - "winreg": "^1.2.3", - "archiver": "latest", - "extract-zip": "latest" + "node-forge": "^0.7.1", + "winreg": "^1.2.3" }, "optionalDependencies": { "screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1" From b36b546c3d39afa6b3679e36d3ad797cb63ae800 Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Mon, 21 Aug 2017 16:12:42 +0530 Subject: [PATCH 8/9] SEARCH-116-GCM 1. Renamed the encrypted file --- js/cryptoLib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index 1006274f..3be28152 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -27,7 +27,7 @@ class Crypto { this.permanentIndexFolderName = 'search_index_' + userId + '_' + INDEX_VERSION; this.dump = TEMPORARY_PATH; this.key = "XrwVgWR4czB1a9scwvgRUNbXiN3W0oWq7oUBenyq7bo="; // temporary only - this.encryptedIndex = 'encryptedIndex.enc'; + this.encryptedIndex = `${INDEX_DATA_FOLDER + '_' + userId + '_' + INDEX_VERSION}.enc`; this.zipErrored = false; } From 8c1c5996992238b0fdb98a63763c13c73375e64a Mon Sep 17 00:00:00 2001 From: Keerthi Niranjan Date: Tue, 22 Aug 2017 12:45:06 +0530 Subject: [PATCH 9/9] SEARCH-116-GCM 1. Fixed userData path issue --- js/cryptoLib/crypto.js | 2 ++ js/cryptoLib/index.js | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/js/cryptoLib/crypto.js b/js/cryptoLib/crypto.js index f7d9feb3..8e1b92cf 100644 --- a/js/cryptoLib/crypto.js +++ b/js/cryptoLib/crypto.js @@ -28,6 +28,8 @@ let validateAndConvertKey = function(key) { if (key && key instanceof Buffer && key.length === KEY_LENGTH) { return key; } else if (key && typeof key === 'string') { + // This is for temporary purpose only. Will be retrieving the key from the backend + // Todo: remove node-forge let md = forge.md.sha256.create(); md.update(key); let bufKey = new Buffer(bits2b64(md.digest().getBytes()), keyEncoding); diff --git a/js/cryptoLib/index.js b/js/cryptoLib/index.js index 3be28152..f05ed8ee 100644 --- a/js/cryptoLib/index.js +++ b/js/cryptoLib/index.js @@ -26,6 +26,7 @@ class Crypto { this.indexDataFolder = INDEX_DATA_FOLDER + '_' + userId + '_' + INDEX_VERSION; this.permanentIndexFolderName = 'search_index_' + userId + '_' + INDEX_VERSION; this.dump = TEMPORARY_PATH; + this.extractToPath = `${TEMPORARY_PATH}/data/${this.permanentIndexFolderName}`; this.key = "XrwVgWR4czB1a9scwvgRUNbXiN3W0oWq7oUBenyq7bo="; // temporary only this.encryptedIndex = `${INDEX_DATA_FOLDER + '_' + userId + '_' + INDEX_VERSION}.enc`; this.zipErrored = false; @@ -82,7 +83,7 @@ class Crypto { zipArchive.pipe(output); - zipArchive.directory(this.indexDataFolder); + zipArchive.directory(this.indexDataFolder + '/', false); zipArchive.finalize((err) => { if (err) { @@ -136,7 +137,7 @@ class Crypto { }); let extractZip = () => { - extract(`${this.dump}/decrypted.zip`, {dir: TEMPORARY_PATH}, (err) => { + extract(`${this.dump}/decrypted.zip`, {dir: `${this.extractToPath}`}, (err) => { if (err) { reject(new Error(err)); }