mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
This commit is contained in:
parent
ac2258769a
commit
0bfa28d795
@ -207,7 +207,13 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) =>
|
||||
setTimeLocale(Setting.value('locale'));
|
||||
}
|
||||
|
||||
if ((action.type === 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type === 'SETTING_UPDATE_ALL')) {
|
||||
// Like the desktop and CLI apps, we run this whenever the sync target properties change.
|
||||
// Previously, this only ran when encryption was enabled/disabled. However, after fetching
|
||||
// a new key, this needs to run and so we run it when the sync target info changes.
|
||||
if (
|
||||
(action.type === 'SETTING_UPDATE_ONE' && (action.key === 'syncInfoCache' || action.key.startsWith('encryption.')))
|
||||
|| action.type === 'SETTING_UPDATE_ALL'
|
||||
) {
|
||||
await loadMasterKeysFromSettings(EncryptionService.instance());
|
||||
void DecryptionWorker.instance().scheduleStart();
|
||||
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
|
||||
|
@ -506,6 +506,13 @@ export default class BaseItem extends BaseModel {
|
||||
masterKeyId: share && share.master_key_id ? share.master_key_id : '',
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'masterKeyNotLoaded' && error.masterKeyId) {
|
||||
this.dispatch?.({
|
||||
type: 'MASTERKEY_ADD_NOT_LOADED',
|
||||
id: error.masterKeyId,
|
||||
});
|
||||
}
|
||||
|
||||
const msg = [`Could not encrypt item ${item.id}`];
|
||||
if (error && error.message) msg.push(error.message);
|
||||
const newError = new Error(msg.join(': '));
|
||||
|
@ -52,6 +52,12 @@ export interface EncryptOptions {
|
||||
masterKeyId?: string;
|
||||
}
|
||||
|
||||
type GetPasswordCallback = ()=> string|Promise<string>;
|
||||
interface EncryptedMasterKey {
|
||||
updatedTime: number;
|
||||
decrypt: ()=> Promise<void>;
|
||||
}
|
||||
|
||||
export default class EncryptionService {
|
||||
|
||||
public static instance_: EncryptionService = null;
|
||||
@ -73,7 +79,8 @@ export default class EncryptionService {
|
||||
// So making the block 10 times smaller make it 100 times faster! So for now using 5KB. This can be
|
||||
// changed easily since the chunk size is incorporated into the encrypted data.
|
||||
private chunkSize_ = 5000;
|
||||
private decryptedMasterKeys_: Record<string, DecryptedMasterKey> = {};
|
||||
private encryptedMasterKeys_: Map<string, EncryptedMasterKey> = new Map();
|
||||
private decryptedMasterKeys_: Map<string, DecryptedMasterKey> = new Map();
|
||||
public defaultEncryptionMethod_ = EncryptionMethod.SJCL1a; // public because used in tests
|
||||
private defaultMasterKeyEncryptionMethod_ = EncryptionMethod.SJCL4;
|
||||
|
||||
@ -96,7 +103,7 @@ export default class EncryptionService {
|
||||
}
|
||||
|
||||
public loadedMasterKeysCount() {
|
||||
return Object.keys(this.decryptedMasterKeys_).length;
|
||||
return this.loadedMasterKeyIds().length;
|
||||
}
|
||||
|
||||
public chunkSize() {
|
||||
@ -123,41 +130,78 @@ export default class EncryptionService {
|
||||
}
|
||||
|
||||
public isMasterKeyLoaded(masterKey: MasterKeyEntity) {
|
||||
const d = this.decryptedMasterKeys_[masterKey.id];
|
||||
if (this.encryptedMasterKeys_.get(masterKey.id)) {
|
||||
return true;
|
||||
}
|
||||
const d = this.decryptedMasterKeys_.get(masterKey.id);
|
||||
if (!d) return false;
|
||||
return d.updatedTime === masterKey.updated_time;
|
||||
}
|
||||
|
||||
public async loadMasterKey(model: MasterKeyEntity, password: string, makeActive = false) {
|
||||
public async loadMasterKey(model: MasterKeyEntity, getPassword: string|GetPasswordCallback, makeActive = false) {
|
||||
if (!model.id) throw new Error('Master key does not have an ID - save it first');
|
||||
|
||||
const loadKey = async () => {
|
||||
logger.info(`Loading master key: ${model.id}. Make active:`, makeActive);
|
||||
|
||||
this.decryptedMasterKeys_[model.id] = {
|
||||
const password = typeof getPassword === 'string' ? getPassword : (await getPassword());
|
||||
if (!password) {
|
||||
logger.info(`Loading master key ${model.id} failed. No valid password found.`);
|
||||
} else {
|
||||
try {
|
||||
this.decryptedMasterKeys_.set(model.id, {
|
||||
plainText: await this.decryptMasterKeyContent(model, password),
|
||||
updatedTime: model.updated_time,
|
||||
};
|
||||
});
|
||||
|
||||
if (makeActive) this.setActiveMasterKeyId(model.id);
|
||||
} catch (error) {
|
||||
logger.warn(`Cannot load master key ${model.id}. Invalid password?`, error);
|
||||
}
|
||||
}
|
||||
|
||||
this.encryptedMasterKeys_.delete(model.id);
|
||||
};
|
||||
|
||||
if (!makeActive) {
|
||||
this.encryptedMasterKeys_.set(model.id, {
|
||||
decrypt: loadKey,
|
||||
updatedTime: model.updated_time,
|
||||
});
|
||||
} else {
|
||||
await loadKey();
|
||||
}
|
||||
}
|
||||
|
||||
public unloadMasterKey(model: MasterKeyEntity) {
|
||||
delete this.decryptedMasterKeys_[model.id];
|
||||
this.decryptedMasterKeys_.delete(model.id);
|
||||
this.encryptedMasterKeys_.delete(model.id);
|
||||
}
|
||||
|
||||
public loadedMasterKey(id: string) {
|
||||
if (!this.decryptedMasterKeys_[id]) {
|
||||
public async loadedMasterKey(id: string) {
|
||||
const cachedKey = this.decryptedMasterKeys_.get(id);
|
||||
if (cachedKey) return cachedKey;
|
||||
|
||||
const decryptCallback = this.encryptedMasterKeys_.get(id);
|
||||
if (decryptCallback) {
|
||||
// TODO: Handle invalid password errors?
|
||||
await decryptCallback.decrypt();
|
||||
}
|
||||
|
||||
const key = this.decryptedMasterKeys_.get(id);
|
||||
|
||||
if (!key) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const error: any = new Error(`Master key is not loaded: ${id}`);
|
||||
error.code = 'masterKeyNotLoaded';
|
||||
error.masterKeyId = id;
|
||||
throw error;
|
||||
}
|
||||
return this.decryptedMasterKeys_[id];
|
||||
return key;
|
||||
}
|
||||
|
||||
public loadedMasterKeyIds() {
|
||||
return Object.keys(this.decryptedMasterKeys_);
|
||||
return [...this.decryptedMasterKeys_.keys(), ...this.encryptedMasterKeys_.keys()];
|
||||
}
|
||||
|
||||
public fsDriver() {
|
||||
@ -430,7 +474,7 @@ export default class EncryptionService {
|
||||
|
||||
const method = options.encryptionMethod;
|
||||
const masterKeyId = options.masterKeyId ? options.masterKeyId : this.activeMasterKeyId();
|
||||
const masterKeyPlainText = this.loadedMasterKey(masterKeyId).plainText;
|
||||
const masterKeyPlainText = (await this.loadedMasterKey(masterKeyId)).plainText;
|
||||
|
||||
const header = {
|
||||
encryptionMethod: method,
|
||||
@ -465,7 +509,7 @@ export default class EncryptionService {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const header: any = await this.decodeHeaderSource_(source);
|
||||
const masterKeyPlainText = this.loadedMasterKey(header.masterKeyId).plainText;
|
||||
const masterKeyPlainText = (await this.loadedMasterKey(header.masterKeyId)).plainText;
|
||||
|
||||
let doneSize = 0;
|
||||
|
||||
|
@ -142,14 +142,10 @@ export async function loadMasterKeysFromSettings(service: EncryptionService) {
|
||||
const mk = masterKeys[i];
|
||||
if (service.isMasterKeyLoaded(mk)) continue;
|
||||
|
||||
await service.loadMasterKey(mk, async () => {
|
||||
const password = await findMasterKeyPassword(service, mk);
|
||||
if (!password) continue;
|
||||
|
||||
try {
|
||||
await service.loadMasterKey(mk, password, activeMasterKeyId === mk.id);
|
||||
} catch (error) {
|
||||
logger.warn(`Cannot load master key ${mk.id}. Invalid password?`, error);
|
||||
}
|
||||
return password;
|
||||
}, activeMasterKeyId === mk.id);
|
||||
}
|
||||
|
||||
logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`);
|
||||
|
Loading…
Reference in New Issue
Block a user