mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-24 08:12:24 +02:00
All: Testing and better handling of E2EE initialisation
This commit is contained in:
parent
f5d26e0d81
commit
4c0b472f67
@ -603,6 +603,7 @@ describe('Synchronizer', function() {
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
if (withEncryption) await loadEncryptionMasterKey(null, true);
|
||||
let note2 = await Note.load(note1.id);
|
||||
note2.todo_completed = time.unixMs()-1;
|
||||
await Note.save(note2);
|
||||
@ -749,4 +750,88 @@ describe('Synchronizer', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should enable encryption automatically when downloading new master key (and none was previously available)', async (done) => {
|
||||
// Enable encryption on client 1 and sync an item
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
await loadEncryptionMasterKey();
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
// Synchronising should enable encryption since we're going to get a master key
|
||||
expect(Setting.value('encryption.enabled')).toBe(false);
|
||||
await synchronizer().start();
|
||||
expect(Setting.value('encryption.enabled')).toBe(true);
|
||||
|
||||
// Check that we got the master key from client 1
|
||||
const masterKey = (await MasterKey.all())[0];
|
||||
expect(!!masterKey).toBe(true);
|
||||
|
||||
// Since client 2 hasn't supplied a password yet, no master key is currently loaded
|
||||
expect(encryptionService().loadedMasterKeyIds().length).toBe(0);
|
||||
|
||||
// If we sync now, nothing should be sent to target since we don't have a password
|
||||
let folder1_2 = await Folder.save({ id: folder1.id, title: "change test" });
|
||||
await synchronizer().start();
|
||||
await switchClient(1);
|
||||
await synchronizer().start();
|
||||
folder1 = await Folder.load(folder1.id);
|
||||
expect(folder1.title).toBe('folder1'); // Still at old value
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
// Now client 2 set the master key password
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
|
||||
// Now that master key should be loaded
|
||||
expect(encryptionService().loadedMasterKeyIds()[0]).toBe(masterKey.id);
|
||||
|
||||
// If we sync now, this time client 1 should get the changes we did earlier
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
// NOTE: there might be a race condition here but can't figure it out. Up to this point all the tests
|
||||
// will pass, which means the master key is loaded. However, the below test find that the title is still
|
||||
// the previous value. Possible reasons are:
|
||||
// - Client 2 didn't send the updated item
|
||||
// - Client 1 didn't receive it
|
||||
// Maybe due to sync_time/updated_time having the same value on one or both of the clients when tests run fast?
|
||||
await synchronizer().start();
|
||||
folder1 = await Folder.load(folder1.id);
|
||||
expect(folder1.title).toBe('change test'); // Got title from client 2
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should encrypt existing notes too when enabling E2EE', async (done) => {
|
||||
// First create a folder, without encryption enabled, and sync it
|
||||
const service = encryptionService();
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
await synchronizer().start();
|
||||
let files = await fileApi().list()
|
||||
expect(files.items[0].content.indexOf('folder1') >= 0).toBe(true)
|
||||
|
||||
// Then enable encryption and sync again
|
||||
let masterKey = await service.generateMasterKey('123456');
|
||||
masterKey = await MasterKey.save(masterKey);
|
||||
await service.initializeEncryption(masterKey, '123456');
|
||||
await service.loadMasterKeysFromSettings();
|
||||
await synchronizer().start();
|
||||
|
||||
// Even though the folder has not been changed it should have been synced again so that
|
||||
// an encrypted version of it replaces the decrypted version.
|
||||
files = await fileApi().list()
|
||||
expect(files.items.length).toBe(2);
|
||||
// By checking that the folder title is not present, we can confirm that the item has indeed been encrypted
|
||||
// One of the two items is the master key
|
||||
expect(files.items[0].content.indexOf('folder1') < 0).toBe(true);
|
||||
expect(files.items[1].content.indexOf('folder1') < 0).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -51,7 +51,7 @@ const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1
|
||||
const logger = new Logger();
|
||||
logger.addTarget('console');
|
||||
logger.addTarget('file', { path: logDir + '/log.txt' });
|
||||
logger.setLevel(Logger.LEVEL_WARN);
|
||||
logger.setLevel(Logger.LEVEL_WARN); // Set to INFO to display sync process in console
|
||||
|
||||
BaseItem.loadClass('Note', Note);
|
||||
BaseItem.loadClass('Folder', Folder);
|
||||
@ -173,11 +173,19 @@ function encryptionService(id = null) {
|
||||
return encryptionServices_[id];
|
||||
}
|
||||
|
||||
async function loadEncryptionMasterKey(id = null) {
|
||||
async function loadEncryptionMasterKey(id = null, useExisting = false) {
|
||||
const service = encryptionService(id);
|
||||
|
||||
let masterKey = await service.generateMasterKey('123456');
|
||||
masterKey = await MasterKey.save(masterKey);
|
||||
let masterKey = null;
|
||||
|
||||
if (!useExisting) { // Create it
|
||||
masterKey = await service.generateMasterKey('123456');
|
||||
masterKey = await MasterKey.save(masterKey);
|
||||
} else { // Use the one already available
|
||||
materKey = await MasterKey.all();
|
||||
if (!materKey.length) throw new Error('No mater key available');
|
||||
masterKey = materKey[0];
|
||||
}
|
||||
|
||||
await service.loadMasterKey(masterKey, '123456', true);
|
||||
|
||||
|
@ -41,14 +41,19 @@ class MasterKeysScreenComponent extends React.Component {
|
||||
renderMasterKey(mk) {
|
||||
const onSaveClick = () => {
|
||||
const password = this.state.passwords[mk.id];
|
||||
const cache = Setting.value('encryption.passwordCache');
|
||||
if (!cache) cache = {};
|
||||
if (!password) {
|
||||
delete cache[mk.id];
|
||||
Setting.deleteObjectKey('encryption.passwordCache', mk.id);
|
||||
} else {
|
||||
cache[mk.id] = password;
|
||||
Setting.setObjectKey('encryption.passwordCache', mk.id, password);
|
||||
}
|
||||
Setting.setValue('encryption.passwordCache', cache);
|
||||
// const cache = Setting.value('encryption.passwordCache');
|
||||
// if (!cache) cache = {};
|
||||
// if (!password) {
|
||||
// delete cache[mk.id];
|
||||
// } else {
|
||||
// cache[mk.id] = password;
|
||||
// }
|
||||
// Setting.setValue('encryption.passwordCache', cache);
|
||||
|
||||
this.checkPasswords();
|
||||
}
|
||||
|
@ -221,6 +221,20 @@ class Setting extends BaseModel {
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
static setObjectKey(settingKey, objectKey, value) {
|
||||
const o = this.value(settingKey);
|
||||
if (typeof o !== 'object') o = {};
|
||||
o[objectKey] = value;
|
||||
this.setValue(settingKey, o);
|
||||
}
|
||||
|
||||
static deleteObjectKey(settingKey, objectKey) {
|
||||
const o = this.value(settingKey);
|
||||
if (typeof o !== 'object') return;
|
||||
delete o[objectKey];
|
||||
this.setValue(settingKey, o);
|
||||
}
|
||||
|
||||
static valueToString(key, value) {
|
||||
const md = this.settingMetadata(key);
|
||||
value = this.formatValue(key, value);
|
||||
|
@ -6,7 +6,7 @@ class DecryptionWorker {
|
||||
this.state_ = 'idle';
|
||||
|
||||
this.dispatch = (action) => {
|
||||
console.warn('DecryptionWorker.dispatch is not defined');
|
||||
//console.warn('DecryptionWorker.dispatch is not defined');
|
||||
};
|
||||
|
||||
this.scheduleId_ = null;
|
||||
|
@ -82,7 +82,11 @@ class EncryptionService {
|
||||
}
|
||||
|
||||
activeMasterKeyId() {
|
||||
if (!this.activeMasterKeyId_) throw new Error('No master key is defined as active');
|
||||
if (!this.activeMasterKeyId_) {
|
||||
const error = new Error('No master key is defined as active. Check this: Either one or more master keys exist but no password was provided for any of them. Or no master key exist. Or master keys and password exist, but none was set as active.');
|
||||
error.code = 'noActiveMasterKey';
|
||||
throw error;
|
||||
}
|
||||
return this.activeMasterKeyId_;
|
||||
}
|
||||
|
||||
|
@ -570,8 +570,14 @@ class Synchronizer {
|
||||
await BaseItem.deleteOrphanSyncItems();
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger().error(error);
|
||||
this.progressReport_.errors.push(error);
|
||||
if (error && error.code === 'noActiveMasterKey') {
|
||||
// Don't log an error for this as this is a common
|
||||
// condition that the UI should report anyway.
|
||||
this.logger().info(error.message);
|
||||
} else {
|
||||
this.logger().error(error);
|
||||
this.progressReport_.errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cancelling()) {
|
||||
@ -588,6 +594,7 @@ class Synchronizer {
|
||||
this.logger().info('Using master key: ', mk);
|
||||
await this.encryptionService().initializeEncryption(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.');
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user