You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	This commit is contained in:
		| @@ -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()}`); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user