1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-08 13:06:15 +02:00

Cli: Ask for master password when encryption or decryption fails

This commit is contained in:
Laurent Cozic 2021-11-22 12:46:57 +00:00
parent 0e11273c45
commit c19e59f5da
5 changed files with 52 additions and 23 deletions

View File

@ -78,6 +78,9 @@ packages/app-cli/app/command-e2ee.js.map
packages/app-cli/app/command-settingschema.d.ts packages/app-cli/app/command-settingschema.d.ts
packages/app-cli/app/command-settingschema.js packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-settingschema.js.map packages/app-cli/app/command-settingschema.js.map
packages/app-cli/app/command-sync.d.ts
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-sync.js.map
packages/app-cli/app/command-testing.d.ts packages/app-cli/app/command-testing.d.ts
packages/app-cli/app/command-testing.js packages/app-cli/app/command-testing.js
packages/app-cli/app/command-testing.js.map packages/app-cli/app/command-testing.js.map

3
.gitignore vendored
View File

@ -61,6 +61,9 @@ packages/app-cli/app/command-e2ee.js.map
packages/app-cli/app/command-settingschema.d.ts packages/app-cli/app/command-settingschema.d.ts
packages/app-cli/app/command-settingschema.js packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-settingschema.js.map packages/app-cli/app/command-settingschema.js.map
packages/app-cli/app/command-sync.d.ts
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-sync.js.map
packages/app-cli/app/command-testing.d.ts packages/app-cli/app/command-testing.d.ts
packages/app-cli/app/command-testing.js packages/app-cli/app/command-testing.js
packages/app-cli/app/command-testing.js.map packages/app-cli/app/command-testing.js.map

View File

@ -6,8 +6,8 @@ import BaseItem from '@joplin/lib/models/BaseItem';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim'; import shim from '@joplin/lib/shim';
import * as pathUtils from '@joplin/lib/path-utils'; import * as pathUtils from '@joplin/lib/path-utils';
import { getEncryptionEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import { getEncryptionEnabled, localSyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { generateMasterKeyAndEnableEncryption, loadMasterKeysFromSettings, setupAndDisableEncryption } from '@joplin/lib/services/e2ee/utils'; import { generateMasterKeyAndEnableEncryption, loadMasterKeysFromSettings, masterPasswordIsValid, setupAndDisableEncryption } from '@joplin/lib/services/e2ee/utils';
const imageType = require('image-type'); const imageType = require('image-type');
const readChunk = require('read-chunk'); const readChunk = require('read-chunk');
@ -40,6 +40,13 @@ class Command extends BaseCommand {
this.stdout(_('Operation cancelled')); this.stdout(_('Operation cancelled'));
return false; return false;
} }
const masterKey = localSyncInfo().masterKeys.find(mk => mk.id === masterKeyId);
if (!(await masterPasswordIsValid(password, masterKey))) {
this.stdout(_('Invalid password'));
return false;
}
Setting.setObjectValue('encryption.passwordCache', masterKeyId, password); Setting.setObjectValue('encryption.passwordCache', masterKeyId, password);
await loadMasterKeysFromSettings(EncryptionService.instance()); await loadMasterKeysFromSettings(EncryptionService.instance());
return true; return true;

View File

@ -1,25 +1,24 @@
import { _ } from '@joplin/lib/locale';
import Setting from '@joplin/lib/models/Setting';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import MigrationHandler from '@joplin/lib/services/synchronizer/MigrationHandler';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import Synchronizer from '@joplin/lib/Synchronizer';
import { masterKeysWithoutPassword } from '@joplin/lib/services/e2ee/utils';
const { BaseCommand } = require('./base-command.js'); const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js'); const { app } = require('./app.js');
const { _ } = require('@joplin/lib/locale');
const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js'); const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js');
const Setting = require('@joplin/lib/models/Setting').default;
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher').default;
const Synchronizer = require('@joplin/lib/Synchronizer').default;
const { reg } = require('@joplin/lib/registry.js'); const { reg } = require('@joplin/lib/registry.js');
const { cliUtils } = require('./cli-utils.js'); const { cliUtils } = require('./cli-utils.js');
const md5 = require('md5'); const md5 = require('md5');
const locker = require('proper-lockfile'); const locker = require('proper-lockfile');
const fs = require('fs-extra'); const fs = require('fs-extra');
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry').default;
const MigrationHandler = require('@joplin/lib/services/synchronizer/MigrationHandler').default;
class Command extends BaseCommand { class Command extends BaseCommand {
constructor() {
super(); private syncTargetId_: number = null;
this.syncTargetId_ = null; private releaseLockFn_: Function = null;
this.releaseLockFn_ = null; private oneDriveApiUtils_: any = null;
this.oneDriveApiUtils_ = null;
}
usage() { usage() {
return 'sync'; return 'sync';
@ -37,9 +36,9 @@ class Command extends BaseCommand {
]; ];
} }
static lockFile(filePath) { static lockFile(filePath: string): Promise<Function> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
locker.lock(filePath, { stale: 1000 * 60 * 5 }, (error, release) => { locker.lock(filePath, { stale: 1000 * 60 * 5 }, (error: any, release: any) => {
if (error) { if (error) {
reject(error); reject(error);
return; return;
@ -50,9 +49,9 @@ class Command extends BaseCommand {
}); });
} }
static isLocked(filePath) { static isLocked(filePath: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
locker.check(filePath, (error, isLocked) => { locker.check(filePath, (error: any, isLocked: boolean) => {
if (error) { if (error) {
reject(error); reject(error);
return; return;
@ -71,7 +70,7 @@ class Command extends BaseCommand {
// OneDrive // OneDrive
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api()); this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
const auth = await this.oneDriveApiUtils_.oauthDance({ const auth = await this.oneDriveApiUtils_.oauthDance({
log: (...s) => { log: (...s: any[]) => {
return this.stdout(...s); return this.stdout(...s);
}, },
}); });
@ -118,7 +117,7 @@ class Command extends BaseCommand {
return !!this.oneDriveApiUtils_; return !!this.oneDriveApiUtils_;
} }
async action(args) { async action(args: any) {
this.releaseLockFn_ = null; this.releaseLockFn_ = null;
// Lock is unique per profile/database // Lock is unique per profile/database
@ -166,12 +165,12 @@ class Command extends BaseCommand {
const sync = await syncTarget.synchronizer(); const sync = await syncTarget.synchronizer();
const options = { const options: any = {
onProgress: report => { onProgress: (report: any) => {
const lines = Synchronizer.reportToLines(report); const lines = Synchronizer.reportToLines(report);
if (lines.length) cliUtils.redraw(lines.join(' ')); if (lines.length) cliUtils.redraw(lines.join(' '));
}, },
onMessage: msg => { onMessage: (msg: string) => {
cliUtils.redrawDone(); cliUtils.redrawDone();
this.stdout(msg); this.stdout(msg);
}, },
@ -238,6 +237,9 @@ class Command extends BaseCommand {
await ResourceFetcher.instance().waitForAllFinished(); await ResourceFetcher.instance().waitForAllFinished();
} }
const noPasswordMkIds = await masterKeysWithoutPassword();
if (noPasswordMkIds.length) this.stdout(`/!\\ ${_('Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.')}`);
await app().refreshCurrentFolder(); await app().refreshCurrentFolder();
} catch (error) { } catch (error) {
cleanUp(); cleanUp();

View File

@ -340,3 +340,17 @@ export async function masterPasswordIsValid(masterPassword: string, activeMaster
// compare to whatever they've entered earlier. // compare to whatever they've entered earlier.
return Setting.value('encryption.masterPassword') === masterPassword; return Setting.value('encryption.masterPassword') === masterPassword;
} }
export async function masterKeysWithoutPassword(): Promise<string[]> {
const syncInfo = localSyncInfo();
const passwordCache = Setting.value('encryption.passwordCache');
const output: string[] = [];
for (const mk of syncInfo.masterKeys) {
if (!masterKeyEnabled(mk)) continue;
const password = await findMasterKeyPassword(EncryptionService.instance(), mk, passwordCache);
if (!password) output.push(mk.id);
}
return output;
}