From 998c35dcb3acdf1444838a58cc958c2a5650ffd0 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Tue, 28 Aug 2018 14:53:05 -0700 Subject: [PATCH] Split configuration into low and high traffic files Also, we're now handling config ourselves instead of using electron-config and config dependencies. --- app/base_config.js | 58 ++++++++++++++++++++++++++++++++++ app/ephemeral_config.js | 12 +++++++ app/key_management.js | 62 ------------------------------------- app/sql_channel.js | 6 ++-- app/user_config.js | 10 +++--- js/views/clear_data_view.js | 19 +++++++----- main.js | 24 +++++++++++--- package.json | 3 +- yarn.lock | 21 ------------- 9 files changed, 112 insertions(+), 103 deletions(-) create mode 100644 app/base_config.js create mode 100644 app/ephemeral_config.js delete mode 100644 app/key_management.js diff --git a/app/base_config.js b/app/base_config.js new file mode 100644 index 0000000000..31f752cf29 --- /dev/null +++ b/app/base_config.js @@ -0,0 +1,58 @@ +const fs = require('fs'); + +const _ = require('lodash'); + +const ENCODING = 'utf8'; + +module.exports = { + start, +}; + +function start(name, targetPath) { + let cachedValue = null; + + try { + const text = fs.readFileSync(targetPath, ENCODING); + cachedValue = JSON.parse(text); + console.log(`config/get: Successfully read ${name} config file`); + + if (!cachedValue) { + console.log( + `config/get: ${name} config value was falsy, cache is now empty object` + ); + cachedValue = Object.create(null); + } + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + + console.log( + `config/get: Did not find ${name} config file, cache is now empty object` + ); + cachedValue = Object.create(null); + } + + function get(keyPath) { + return _.get(cachedValue, keyPath); + } + + function set(keyPath, value) { + _.set(cachedValue, keyPath, value); + console.log(`config/set: Saving ${name} config to disk`); + const text = JSON.stringify(cachedValue, null, ' '); + fs.writeFileSync(targetPath, text, ENCODING); + } + + function remove() { + console.log(`config/remove: Deleting ${name} config from disk`); + fs.unlinkSync(targetPath); + cachedValue = Object.create(null); + } + + return { + set, + get, + remove, + }; +} diff --git a/app/ephemeral_config.js b/app/ephemeral_config.js new file mode 100644 index 0000000000..b2b91cb5a1 --- /dev/null +++ b/app/ephemeral_config.js @@ -0,0 +1,12 @@ +const path = require('path'); + +const { app } = require('electron'); + +const { start } = require('./base_config'); + +const userDataPath = app.getPath('userData'); +const targetPath = path.join(userDataPath, 'ephemeral.json'); + +const ephemeralConfig = start('ephemeral', targetPath); + +module.exports = ephemeralConfig; diff --git a/app/key_management.js b/app/key_management.js deleted file mode 100644 index 83a471f342..0000000000 --- a/app/key_management.js +++ /dev/null @@ -1,62 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -const { app } = require('electron'); - -const ENCODING = 'utf8'; -const userDataPath = app.getPath('userData'); -const targetPath = path.join(userDataPath, 'key.txt'); - -module.exports = { - get, - set, - initialize, - remove, -}; - -function get() { - try { - const key = fs.readFileSync(targetPath, ENCODING); - console.log('key/get: Successfully read key file'); - return key; - } catch (error) { - if (error.code === 'ENOENT') { - console.log('key/get: Could not find key file, returning null'); - return null; - } - - throw error; - } -} - -function set(key) { - console.log('key/set: Saving key to disk'); - fs.writeFileSync(targetPath, key, ENCODING); -} - -function remove() { - console.log('key/remove: Deleting key from disk'); - fs.unlinkSync(targetPath); -} - -function initialize({ userConfig }) { - const keyFromConfig = userConfig.get('key'); - const keyFromStore = get(); - - let key = keyFromStore || keyFromConfig; - if (!key) { - console.log( - 'key/initialize: Generating new encryption key, since we did not find it on disk' - ); - // https://www.zetetic.net/sqlcipher/sqlcipher-api/#key - key = crypto.randomBytes(32).toString('hex'); - set(key); - } else if (keyFromConfig) { - set(key); - console.log('key/initialize: Removing key from config.json'); - userConfig.delete('key'); - } - - return key; -} diff --git a/app/sql_channel.js b/app/sql_channel.js index 46d74c31b7..128ab3a594 100644 --- a/app/sql_channel.js +++ b/app/sql_channel.js @@ -1,6 +1,7 @@ const electron = require('electron'); const sql = require('./sql'); -const { remove } = require('./key_management'); +const { remove: removeUserConfig } = require('./user_config'); +const { remove: removeEphemeralConfig } = require('./ephemeral_config'); const { ipcMain } = electron; @@ -41,7 +42,8 @@ function initialize() { ipcMain.on(ERASE_SQL_KEY, async event => { try { - remove(); + removeUserConfig(); + removeEphemeralConfig(); event.sender.send(`${ERASE_SQL_KEY}-done`); } catch (error) { const errorForDisplay = error && error.stack ? error.stack : error; diff --git a/app/user_config.js b/app/user_config.js index 5413e89ed3..4da1f62a08 100644 --- a/app/user_config.js +++ b/app/user_config.js @@ -1,11 +1,11 @@ const path = require('path'); const { app } = require('electron'); -const ElectronConfig = require('electron-config'); +const { start } = require('./base_config'); const config = require('./config'); -// use a separate data directory for development +// Use separate data directory for development if (config.has('storageProfile')) { const userData = path.join( app.getPath('appData'), @@ -17,7 +17,9 @@ if (config.has('storageProfile')) { console.log(`userData: ${app.getPath('userData')}`); -// this needs to be below our update to the appData path -const userConfig = new ElectronConfig(); +const userDataPath = app.getPath('userData'); +const targetPath = path.join(userDataPath, 'config.json'); + +const userConfig = start('user', targetPath); module.exports = userConfig; diff --git a/js/views/clear_data_view.js b/js/views/clear_data_view.js index e7b30cd26c..ba1e4b541b 100644 --- a/js/views/clear_data_view.js +++ b/js/views/clear_data_view.js @@ -34,11 +34,15 @@ this.render(); try { + await Database.clear(); await Database.close(); - window.log.info('All database connections closed. Starting delete.'); + window.log.info( + 'All database connections closed. Starting database drop.' + ); + await Database.drop(); } catch (error) { window.log.error( - 'Something went wrong closing all database connections.' + 'Something went wrong deleting IndexedDB data then dropping database.' ); } @@ -46,15 +50,14 @@ }, async clearAllData() { try { - await Promise.all([ - Logs.deleteAll(), - Database.drop(), - window.Signal.Data.removeAll(), - window.Signal.Data.removeOtherData(), - ]); + await Logs.deleteAll(); + // SQLCipher + await window.Signal.Data.removeAll(); await window.Signal.Data.close(); await window.Signal.Data.removeDB(); + + await window.Signal.Data.removeOtherData(); } catch (error) { window.log.error( 'Something went wrong deleting all data:', diff --git a/main.js b/main.js index 53269fbb1f..1add765f0a 100644 --- a/main.js +++ b/main.js @@ -4,6 +4,7 @@ const path = require('path'); const url = require('url'); const os = require('os'); const fs = require('fs'); +const crypto = require('crypto'); const _ = require('lodash'); const pify = require('pify'); @@ -62,7 +63,7 @@ const attachments = require('./app/attachments'); const attachmentChannel = require('./app/attachment_channel'); const autoUpdate = require('./app/auto_update'); const createTrayIcon = require('./app/tray_icon'); -const keyManagement = require('./app/key_management'); +const ephemeralConfig = require('./app/ephemeral_config'); const logging = require('./app/logging'); const sql = require('./app/sql'); const sqlChannels = require('./app/sql_channel'); @@ -115,7 +116,14 @@ if (!process.mas) { } } -let windowConfig = userConfig.get('window'); +const windowFromUserConfig = userConfig.get('window'); +const windowFromEphemeral = ephemeralConfig.get('window'); +let windowConfig = windowFromEphemeral || windowFromUserConfig; +if (windowFromUserConfig) { + userConfig.set('window', null); + ephemeralConfig.set('window', windowConfig); +} + const loadLocale = require('./app/locale').load; // Both of these will be set after app fires the 'ready' event @@ -285,7 +293,7 @@ function createWindow() { 'Updating BrowserWindow config: %s', JSON.stringify(windowConfig) ); - userConfig.set('window', windowConfig); + ephemeralConfig.set('window', windowConfig); } const debouncedCaptureStats = _.debounce(captureAndSaveWindowStats, 500); @@ -619,7 +627,15 @@ app.on('ready', async () => { locale = loadLocale({ appLocale, logger }); } - const key = keyManagement.initialize({ userConfig }); + let key = userConfig.get('key'); + if (!key) { + console.log( + 'key/initialize: Generating new encryption key, since we did not find it on disk' + ); + // https://www.zetetic.net/sqlcipher/sqlcipher-api/#key + key = crypto.randomBytes(32).toString('hex'); + userConfig.set('key', key); + } await sql.initialize({ configDir: userDataPath, key }); await sqlChannels.initialize(); diff --git a/package.json b/package.json index 63b17699c6..19b9d422d8 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "styleguide": "styleguidist server" }, "dependencies": { + "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#ed4f4d179ac010c6347b291cbd4c2ebe5c773741", "@sindresorhus/is": "^0.8.0", "archiver": "^2.1.1", "backbone": "^1.3.3", @@ -49,10 +50,8 @@ "bunyan": "^1.8.12", "classnames": "^2.2.5", "config": "^1.28.1", - "electron-config": "^1.0.0", "electron-editor-context-menu": "^1.1.1", "electron-is-dev": "^0.3.0", - "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#ed4f4d179ac010c6347b291cbd4c2ebe5c773741", "electron-unhandled": "https://github.com/scottnonnenberg-signal/electron-unhandled.git#7496187472aa561d39fcd4c843a54ffbef0a388c", "electron-updater": "^2.21.10", "emoji-datasource": "4.0.0", diff --git a/yarn.lock b/yarn.lock index 08d2ceb918..49490be7b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1647,15 +1647,6 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -conf@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/conf/-/conf-1.1.1.tgz#238d0a3090ac4916ed2d40c7e81d7a11667bc7ba" - dependencies: - dot-prop "^4.1.0" - env-paths "^1.0.0" - make-dir "^1.0.0" - pkg-up "^2.0.0" - config@^1.28.1: version "1.28.1" resolved "https://registry.yarnpkg.com/config/-/config-1.28.1.tgz#7625d2a1e4c90f131d8a73347982d93c3873282d" @@ -2423,12 +2414,6 @@ electron-chromedriver@~1.8.0: electron-download "^4.1.0" extract-zip "^1.6.5" -electron-config@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/electron-config/-/electron-config-1.0.0.tgz#069d044cc794f04784ae72f12916725d3c8c39af" - dependencies: - conf "^1.0.0" - electron-download-tf@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/electron-download-tf/-/electron-download-tf-4.3.4.tgz#b03740b2885aa2ad3f8784fae74df427f66d5165" @@ -6428,12 +6413,6 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - dependencies: - find-up "^2.1.0" - pkginfo@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.0.tgz#349dbb7ffd38081fcadc0853df687f0c7744cd65"