All: Fixed various issues regarding encryption and decryptino of resources, and started doing GUI for Electron app

pull/138/head^2
Laurent Cozic 2017-12-21 20:06:08 +01:00
parent 7750b954fc
commit 6683da804b
12 changed files with 81 additions and 89 deletions

View File

@ -14,6 +14,7 @@ const chalk = require('chalk');
const tk = require('terminal-kit');
const TermWrapper = require('tkwidgets/framework/TermWrapper.js');
const Renderer = require('tkwidgets/framework/Renderer.js');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseWidget = require('tkwidgets/BaseWidget.js');
const ListWidget = require('tkwidgets/ListWidget.js');
@ -65,6 +66,7 @@ class AppGui {
// a regular command it's not necessary since the process
// exits right away.
reg.setupRecurrentSync();
DecryptionWorker.instance().scheduleStart();
}
store() {

View File

@ -267,8 +267,19 @@ class Application extends BaseApplication {
routeName: 'Status',
});
}
}, {
type: 'separator',
screens: ['Main'],
},{
label: _('Options'),
label: _('Encryption options'),
click: () => {
this.dispatch({
type: 'NAV_GO',
routeName: 'EncryptionConfig',
});
}
},{
label: _('General Options'),
click: () => {
this.dispatch({
type: 'NAV_GO',
@ -276,25 +287,6 @@ class Application extends BaseApplication {
});
}
}],
}, {
label: _('Encryption'),
submenu: [{
label: _('Enable'),
click: () => {
// this.dispatch({
// type: 'NAV_GO',
// routeName: 'MasterKeys',
// });
}
},{
label: _('Master Keys'),
click: () => {
this.dispatch({
type: 'NAV_GO',
routeName: 'MasterKeys',
});
}
}],
}, {
label: _('Help'),
submenu: [{
@ -414,6 +406,8 @@ class Application extends BaseApplication {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
AlarmService.updateAllNotifications();
DecryptionWorker.instance().scheduleStart();
});
}
}

View File

@ -7,7 +7,7 @@ const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
const { time } = require('lib/time-utils.js');
class MasterKeysScreenComponent extends React.Component {
class EncryptionConfigScreenComponent extends React.Component {
constructor() {
super();
@ -131,6 +131,6 @@ const mapStateToProps = (state) => {
};
};
const MasterKeysScreen = connect(mapStateToProps)(MasterKeysScreenComponent);
const EncryptionConfigScreen = connect(mapStateToProps)(EncryptionConfigScreenComponent);
module.exports = { MasterKeysScreen };
module.exports = { EncryptionConfigScreen };

View File

@ -346,7 +346,7 @@ class MainScreenComponent extends React.Component {
const onViewMasterKeysClick = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'MasterKeys',
routeName: 'EncryptionConfig',
});
}

View File

@ -11,7 +11,7 @@ const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
const { StatusScreen } = require('./StatusScreen.min.js');
const { ImportScreen } = require('./ImportScreen.min.js');
const { ConfigScreen } = require('./ConfigScreen.min.js');
const { MasterKeysScreen } = require('./MasterKeysScreen.min.js');
const { EncryptionConfigScreen } = require('./EncryptionConfigScreen.min.js');
const { Navigator } = require('./Navigator.min.js');
const { app } = require('../app');
@ -78,7 +78,7 @@ class RootComponent extends React.Component {
Import: { screen: ImportScreen, title: () => _('Import') },
Config: { screen: ConfigScreen, title: () => _('Options') },
Status: { screen: StatusScreen, title: () => _('Synchronisation Status') },
MasterKeys: { screen: MasterKeysScreen, title: () => _('Master Keys') },
EncryptionConfig: { screen: EncryptionConfigScreen, title: () => _('Encryption Options') },
};
return (

View File

@ -8,7 +8,14 @@ Encrypted data is encoded to ASCII because encryption/decryption functions in Re
Name | Size
-------------------|-------------------------
Identifier | 3 chars ("JED")
Version number | 2 chars (Hexa string)
This is followed by the encryption metadata:
Name | Size
-------------------|-------------------------
Length | 6 chars (Hexa string)
Encryption method | 2 chars (Hexa string)
Master key ID | 32 chars (Hexa string)

View File

@ -268,14 +268,16 @@ class BaseApplication {
}
if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) {
await EncryptionService.instance().loadMasterKeysFromSettings();
DecryptionWorker.instance().scheduleStart();
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
if (this.hasGui()) {
await EncryptionService.instance().loadMasterKeysFromSettings();
DecryptionWorker.instance().scheduleStart();
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
this.dispatch({
type: 'MASTERKEY_REMOVE_MISSING',
ids: loadedMasterKeyIds,
});
this.dispatch({
type: 'MASTERKEY_REMOVE_MISSING',
ids: loadedMasterKeyIds,
});
}
}
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
@ -302,6 +304,10 @@ class BaseApplication {
reg.setupRecurrentSync();
}
if (this.hasGui() && action.type === 'SYNC_GOT_ENCRYPTED_ITEM') {
DecryptionWorker.instance().scheduleStart();
}
return result;
}
@ -411,7 +417,6 @@ class BaseApplication {
DecryptionWorker.instance().setLogger(this.logger_);
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
await EncryptionService.instance().loadMasterKeysFromSettings();
DecryptionWorker.instance().scheduleStart();
let currentFolderId = Setting.value('activeFolderId');
let currentFolder = null;

View File

@ -46,11 +46,10 @@ class FsDriverNode {
async readFileChunk(handle, length, encoding = 'base64') {
let buffer = new Buffer(length);
const bytesRead = await fs.read(handle, buffer, 0, length, null)
if (!bytesRead) return null;
const output = buffer.slice(0, bytesRead);
if (encoding === 'base64') return output.toString('base64');
if (encoding === 'ascii') return output.toString('ascii');
const result = await fs.read(handle, buffer, 0, length, null)
if (!result.bytesRead) return null;
if (encoding === 'base64') return buffer.toString('base64');
if (encoding === 'ascii') return buffer.toString('ascii');
throw new Error('Unsupported encoding: ' + encoding);
}

View File

@ -268,20 +268,17 @@ class BaseItem extends BaseModel {
const cipherText = await this.encryptionService().encryptString(serialized);
const reducedItem = Object.assign({}, item);
//const reducedItem = Object.assign({}, item);
// List of keys that won't be encrypted - mostly foreign keys required to link items
// with each others and timestamp required for synchronisation.
const keepKeys = ['id', 'note_id', 'tag_id', 'parent_id', 'updated_time', 'type_'];
const reducedItem = {};
for (let n in reducedItem) {
if (!reducedItem.hasOwnProperty(n)) continue;
if (keepKeys.indexOf(n) >= 0) {
continue;
} else {
delete reducedItem[n];
}
for (let i = 0; i < keepKeys.length; i++) {
const n = keepKeys[i];
if (!item.hasOwnProperty(n)) continue;
reducedItem[n] = item[n];
}
reducedItem.encryption_applied = 1;
@ -370,7 +367,7 @@ class BaseItem extends BaseModel {
const className = classNames[i];
const ItemClass = this.getClass(className);
const whereSql = ['encryption_applied = 1'];
const whereSql = className === 'Resource' ? ['(encryption_blob_encrypted = 1 OR encryption_applied = 1)'] : ['encryption_applied = 1'];
if (exclusions.length) whereSql.push('id NOT IN ("' + exclusions.join('","') + '")');
const sql = sprintf(`

View File

@ -53,7 +53,9 @@ class Resource extends BaseItem {
// For resources, we need to decrypt the item (metadata) and the resource binary blob.
static async decrypt(item) {
const decryptedItem = await super.decrypt(item);
// The item might already be decrypted but not the blob (for instance if it crashes while
// decrypting the blob or was otherwise interrupted).
const decryptedItem = item.encryption_cipher_text ? await super.decrypt(item) : Object.assign({}, item);
if (!decryptedItem.encryption_blob_encrypted) return decryptedItem;
const plainTextPath = this.fullPath(decryptedItem);
@ -69,7 +71,7 @@ class Resource extends BaseItem {
}
await this.encryptionService().decryptFile(encryptedPath, plainTextPath);
item.encryption_blob_encrypted = 0;
decryptedItem.encryption_blob_encrypted = 0;
return Resource.save(decryptedItem, { autoTimestamp: false });
}

View File

@ -300,21 +300,16 @@ class EncryptionService {
const mdSizeHex = await source.read(6);
const md = await source.read(parseInt(mdSizeHex, 16));
const header = this.decodeHeader_(identifier + mdSizeHex + md);
const masterKeyPlainText = this.loadedMasterKey(header.masterKeyId);
let index = header.length;
while (true) {
const lengthHex = await source.read(6);
if (!lengthHex) break;
if (lengthHex.length !== 6) throw new Error('Invalid block size: ' + lengthHex);
const length = parseInt(lengthHex, 16);
index += 6;
if (!length) continue; // Weird but could be not completely invalid (block of size 0) so continue decrypting
const block = await source.read(length);
index += length;
const plainText = await this.decrypt(header.encryptionMethod, masterKeyPlainText, block);
await destination.append(plainText);
@ -349,25 +344,23 @@ class EncryptionService {
}
async fileReader_(path, encoding) {
const fsDriver = this.fsDriver();
const handle = await fsDriver.open(path, 'r');
const handle = await this.fsDriver().open(path, 'r');
const reader = {
handle: handle,
read: async function(size) {
return fsDriver.readFileChunk(reader.handle, size, encoding);
read: async (size) => {
return this.fsDriver().readFileChunk(reader.handle, size, encoding);
},
close: function() {
fsDriver.close(reader.handle);
close: () => {
this.fsDriver().close(reader.handle);
},
};
return reader;
}
async fileWriter_(path, encoding) {
const fsDriver = this.fsDriver();
return {
append: async function(data) {
return fsDriver.appendFile(path, data, encoding);
append: async (data) => {
return this.fsDriver().appendFile(path, data, encoding);
},
close: function() {},
};
@ -388,7 +381,6 @@ class EncryptionService {
}
async encryptFile(srcPath, destPath) {
const fsDriver = this.fsDriver();
let source = await this.fileReader_(srcPath, 'base64');
let destination = await this.fileWriter_(destPath, 'ascii');
@ -400,11 +392,11 @@ class EncryptionService {
}
try {
await fsDriver.unlink(destPath);
await this.fsDriver().unlink(destPath);
await this.encryptAbstract_(source, destination);
} catch (error) {
cleanUp();
await fsDriver.unlink(destPath);
await this.fsDriver().unlink(destPath);
throw error;
}
@ -412,7 +404,6 @@ class EncryptionService {
}
async decryptFile(srcPath, destPath) {
const fsDriver = this.fsDriver();
let source = await this.fileReader_(srcPath, 'ascii');
let destination = await this.fileWriter_(destPath, 'base64');
@ -424,11 +415,11 @@ class EncryptionService {
}
try {
await fsDriver.unlink(destPath);
await this.fsDriver().unlink(destPath);
await this.decryptAbstract_(source, destination);
} catch (error) {
cleanUp();
await fsDriver.unlink(destPath);
await this.fsDriver().unlink(destPath);
throw error;
}
@ -461,7 +452,7 @@ class EncryptionService {
const reader = this.stringReader_(headerHexaBytes, true);
const identifier = reader.read(3);
const version = parseInt(reader.read(2), 16);
if (identifier !== 'JED') throw new Error('Invalid header (missing identifier)');
if (identifier !== 'JED') throw new Error('Invalid header (missing identifier): ' + headerHexaBytes.substr(0,64));
const template = this.headerTemplate(version);
const size = parseInt(reader.read(6), 16);

View File

@ -181,6 +181,7 @@ class Synchronizer {
this.cancelling_ = false;
const masterKeysBefore = await MasterKey.count();
let hasAutoEnabledEncryption = false;
// ------------------------------------------------------------------------
// First, find all the items that have been changed since the
@ -508,6 +509,17 @@ class Synchronizer {
await ItemClass.save(content, options);
if (!hasAutoEnabledEncryption && content.type_ === BaseModel.TYPE_MASTER_KEY && !masterKeysBefore) {
hasAutoEnabledEncryption = true;
this.logger().info('One master key was downloaded and none was previously available: automatically enabling encryption');
this.logger().info('Using master key: ', content);
await this.encryptionService().enableEncryption(content);
await this.encryptionService().loadMasterKeysFromSettings();
this.logger().info('Encryption has been enabled with downloaded master key as active key. However, note that no password was initially supplied. It will need to be provided by user.');
}
if (!!content.encryption_applied) this.dispatch({ type: 'SYNC_GOT_ENCRYPTED_ITEM' });
} else if (action == 'deleteLocal') {
if (local.type_ == BaseModel.TYPE_FOLDER) {
@ -575,23 +587,6 @@ class Synchronizer {
this.cancelling_ = false;
}
const masterKeysAfter = await MasterKey.count();
if (!masterKeysBefore && masterKeysAfter) {
this.logger().info('One master key was downloaded and none was previously available: automatically enabling encryption');
const mk = await MasterKey.latest();
if (mk) {
this.logger().info('Using master key: ', mk);
await this.encryptionService().enableEncryption(mk);
await this.encryptionService().loadMasterKeysFromSettings();
this.logger().info('Encryption has been enabled with downloaded master key as active key. However, note that no password was initially supplied. It will need to be provided by user.');
}
}
if (masterKeysAfter && this.autoStartDecryptionWorker_) {
DecryptionWorker.instance().scheduleStart();
}
this.progressReport_.completedTime = time.unixMs();
this.logSyncOperation('finished', null, null, 'Synchronisation finished [' + synchronizationId + ']');