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')); | 		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()); | 		await loadMasterKeysFromSettings(EncryptionService.instance()); | ||||||
| 		void DecryptionWorker.instance().scheduleStart(); | 		void DecryptionWorker.instance().scheduleStart(); | ||||||
| 		const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds(); | 		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 : '', | 				masterKeyId: share && share.master_key_id ? share.master_key_id : '', | ||||||
| 			}); | 			}); | ||||||
| 		} catch (error) { | 		} 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}`]; | 			const msg = [`Could not encrypt item ${item.id}`]; | ||||||
| 			if (error && error.message) msg.push(error.message); | 			if (error && error.message) msg.push(error.message); | ||||||
| 			const newError = new Error(msg.join(': ')); | 			const newError = new Error(msg.join(': ')); | ||||||
|   | |||||||
| @@ -52,6 +52,12 @@ export interface EncryptOptions { | |||||||
| 	masterKeyId?: string; | 	masterKeyId?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type GetPasswordCallback = ()=> string|Promise<string>; | ||||||
|  | interface EncryptedMasterKey { | ||||||
|  | 	updatedTime: number; | ||||||
|  | 	decrypt: ()=> Promise<void>; | ||||||
|  | } | ||||||
|  |  | ||||||
| export default class EncryptionService { | export default class EncryptionService { | ||||||
|  |  | ||||||
| 	public static instance_: EncryptionService = null; | 	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 | 	// 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. | 	// changed easily since the chunk size is incorporated into the encrypted data. | ||||||
| 	private chunkSize_ = 5000; | 	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 | 	public defaultEncryptionMethod_ = EncryptionMethod.SJCL1a; // public because used in tests | ||||||
| 	private defaultMasterKeyEncryptionMethod_ = EncryptionMethod.SJCL4; | 	private defaultMasterKeyEncryptionMethod_ = EncryptionMethod.SJCL4; | ||||||
|  |  | ||||||
| @@ -96,7 +103,7 @@ export default class EncryptionService { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public loadedMasterKeysCount() { | 	public loadedMasterKeysCount() { | ||||||
| 		return Object.keys(this.decryptedMasterKeys_).length; | 		return this.loadedMasterKeyIds().length; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public chunkSize() { | 	public chunkSize() { | ||||||
| @@ -123,41 +130,78 @@ export default class EncryptionService { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public isMasterKeyLoaded(masterKey: MasterKeyEntity) { | 	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; | 		if (!d) return false; | ||||||
| 		return d.updatedTime === masterKey.updated_time; | 		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'); | 		if (!model.id) throw new Error('Master key does not have an ID - save it first'); | ||||||
|  |  | ||||||
| 		logger.info(`Loading master key: ${model.id}. Make active:`, makeActive); | 		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()); | ||||||
| 			plainText: await this.decryptMasterKeyContent(model, password), | 			if (!password) { | ||||||
| 			updatedTime: model.updated_time, | 				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.setActiveMasterKeyId(model.id); | 		if (!makeActive) { | ||||||
|  | 			this.encryptedMasterKeys_.set(model.id, { | ||||||
|  | 				decrypt: loadKey, | ||||||
|  | 				updatedTime: model.updated_time, | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			await loadKey(); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public unloadMasterKey(model: MasterKeyEntity) { | 	public unloadMasterKey(model: MasterKeyEntity) { | ||||||
| 		delete this.decryptedMasterKeys_[model.id]; | 		this.decryptedMasterKeys_.delete(model.id); | ||||||
|  | 		this.encryptedMasterKeys_.delete(model.id); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public loadedMasterKey(id: string) { | 	public async loadedMasterKey(id: string) { | ||||||
| 		if (!this.decryptedMasterKeys_[id]) { | 		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 | 			// 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}`); | 			const error: any = new Error(`Master key is not loaded: ${id}`); | ||||||
| 			error.code = 'masterKeyNotLoaded'; | 			error.code = 'masterKeyNotLoaded'; | ||||||
| 			error.masterKeyId = id; | 			error.masterKeyId = id; | ||||||
| 			throw error; | 			throw error; | ||||||
| 		} | 		} | ||||||
| 		return this.decryptedMasterKeys_[id]; | 		return key; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public loadedMasterKeyIds() { | 	public loadedMasterKeyIds() { | ||||||
| 		return Object.keys(this.decryptedMasterKeys_); | 		return [...this.decryptedMasterKeys_.keys(), ...this.encryptedMasterKeys_.keys()]; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public fsDriver() { | 	public fsDriver() { | ||||||
| @@ -430,7 +474,7 @@ export default class EncryptionService { | |||||||
|  |  | ||||||
| 		const method = options.encryptionMethod; | 		const method = options.encryptionMethod; | ||||||
| 		const masterKeyId = options.masterKeyId ? options.masterKeyId : this.activeMasterKeyId(); | 		const masterKeyId = options.masterKeyId ? options.masterKeyId : this.activeMasterKeyId(); | ||||||
| 		const masterKeyPlainText = this.loadedMasterKey(masterKeyId).plainText; | 		const masterKeyPlainText = (await this.loadedMasterKey(masterKeyId)).plainText; | ||||||
|  |  | ||||||
| 		const header = { | 		const header = { | ||||||
| 			encryptionMethod: method, | 			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 | 		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||||
| 		const header: any = await this.decodeHeaderSource_(source); | 		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; | 		let doneSize = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -142,14 +142,10 @@ export async function loadMasterKeysFromSettings(service: EncryptionService) { | |||||||
| 		const mk = masterKeys[i]; | 		const mk = masterKeys[i]; | ||||||
| 		if (service.isMasterKeyLoaded(mk)) continue; | 		if (service.isMasterKeyLoaded(mk)) continue; | ||||||
|  |  | ||||||
| 		const password = await findMasterKeyPassword(service, mk); | 		await service.loadMasterKey(mk, async () => { | ||||||
| 		if (!password) continue; | 			const password = await findMasterKeyPassword(service, mk); | ||||||
|  | 			return password; | ||||||
| 		try { | 		}, activeMasterKeyId === mk.id); | ||||||
| 			await service.loadMasterKey(mk, password, activeMasterKeyId === mk.id); |  | ||||||
| 		} catch (error) { |  | ||||||
| 			logger.warn(`Cannot load master key ${mk.id}. Invalid password?`, error); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`); | 	logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user