mirror of
https://github.com/laurent22/joplin.git
synced 2026-10-25 02:47:28 +00:00
Desktop, Mobile: Resolves #16710: Improve E2EE UI when a key is stored in an unknown format (#16711)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
1 parent
9e4ca948be
commit
ab241bd8d6
9 files changed
+165
-27
No files matched your search
@@ -3,7 +3,7 @@ import { _ } from '../../locale';
|
||||
import BaseItem, { EncryptedItemsStats } from '../../models/BaseItem';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '../../hooks/useAsyncEffect';
|
||||
import { MasterKeyEntity } from '../../services/e2ee/types';
|
||||
import { findMasterKeyPassword, getMasterPasswordStatus, loadMasterKeysFromSettings, masterPasswordIsValid, MasterPasswordStatus } from '../../services/e2ee/utils';
|
||||
import { findMasterKeyPassword, getMasterPasswordStatus, isKnownEncryptionMethod, loadMasterKeysFromSettings, masterPasswordIsValid, MasterPasswordStatus } from '../../services/e2ee/utils';
|
||||
import EncryptionService from '../../services/e2ee/EncryptionService';
|
||||
import { masterKeyEnabled, setMasterKeyEnabled } from '../../services/synchronizer/syncInfoUtils';
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
@@ -212,11 +212,29 @@ export const useNeedMasterPassword = (passwordChecks: PasswordChecks, masterKeys
|
||||
const mk = masterKeys.find(mk => mk.id === mkId);
|
||||
if (!mk) continue;
|
||||
if (!masterKeyEnabled(mk)) continue;
|
||||
if (!valid) return true;
|
||||
if (!valid && isKnownEncryptionMethod(mk.encryption_method)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const useHasUnknownEncryptionMethodKeys = (masterKeys: MasterKeyEntity[]) => {
|
||||
for (const mk of masterKeys) {
|
||||
if (mk && masterKeyEnabled(mk) && !isKnownEncryptionMethod(mk.encryption_method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const useHasUnknownEncryptionMethodKeysMessage = (masterKeys: MasterKeyEntity[]) => {
|
||||
const hasUnknownEncryptionMethodKeys = useHasUnknownEncryptionMethodKeys(masterKeys);
|
||||
if (!hasUnknownEncryptionMethodKeys) return '';
|
||||
return [
|
||||
_('One or more encryption keys are stored in an unknown format.'),
|
||||
_('These keys were most likely created by a newer version of Joplin. Please check that Joplin has been updated to the latest version.'),
|
||||
].join('\n\n');
|
||||
};
|
||||
|
||||
export const determineKeyPassword = (masterKeyId: string, masterPasswordKeys: PasswordChecks, masterPassword: string, passwords: Record<string, string>): string => {
|
||||
if (masterPasswordKeys[masterKeyId]) return masterPassword;
|
||||
return passwords[masterKeyId];
|
||||
|
||||
@@ -286,7 +286,10 @@ export default class EncryptionService {
|
||||
}
|
||||
|
||||
public masterKeysThatNeedUpgrading(masterKeys: MasterKeyEntity[]) {
|
||||
return MasterKey.allWithoutEncryptionMethod(masterKeys, [this.defaultMasterKeyEncryptionMethod_, EncryptionMethod.Custom]);
|
||||
const keysWithNonDefaultMethods = MasterKey.allWithoutEncryptionMethod(masterKeys, [
|
||||
this.defaultMasterKeyEncryptionMethod_, EncryptionMethod.Custom,
|
||||
]);
|
||||
return keysWithNonDefaultMethods.filter(key => key.encryption_method in EncryptionMethod);
|
||||
}
|
||||
|
||||
public async reencryptMasterKey(model: MasterKeyEntity, decryptionPassword: string, encryptionPassword: string, decryptOptions: EncryptOptions = null, encryptOptions: EncryptOptions = null): Promise<MasterKeyEntity> {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService, expectNotThrow, expectThrow, kvStore, msleep } from '../../testing/test-utils';
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
import { activeMasterKeySanityCheck, migrateMasterPassword, migratePpk, resetMasterPassword, showMissingMasterKeyMessage, updateMasterPassword } from './utils';
|
||||
import { activeMasterKeySanityCheck, migrateMasterPassword, migratePpk, resetMasterPassword, showMissingMasterKeyMessage, showUnknownKeyFormatBanner, updateMasterPassword } from './utils';
|
||||
import { localSyncInfo, masterKeyById, masterKeyEnabled, saveLocalSyncInfo, setActiveMasterKeyId, setMasterKeyEnabled, setPpk } from '../synchronizer/syncInfoUtils';
|
||||
import Setting from '../../models/Setting';
|
||||
import { generateKeyPair, generateKeyPairWithAlgorithm, getPpkAlgorithm, ppkPasswordIsValid, testing__setPpkMigrations_ } from './ppk/ppk';
|
||||
import { PublicKeyAlgorithm } from './types';
|
||||
import { EncryptionMethod } from './EncryptionService';
|
||||
|
||||
describe('e2ee/utils', () => {
|
||||
|
||||
@@ -43,6 +44,19 @@ describe('e2ee/utils', () => {
|
||||
expect(showMissingMasterKeyMessage(syncInfo, [mk1.id, mk2.id])).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[EncryptionMethod.KeyV1, false],
|
||||
[1234, true],
|
||||
[{ toString: 'throws if converted to string with String(...)' }, true],
|
||||
])('should decide whether to show the unknown key format banner (encryption method: %j, should show: %j)', async (encryptionMethod, shouldShow) => {
|
||||
await MasterKey.save({
|
||||
...await encryptionService().generateMasterKey('111111'),
|
||||
encryption_method: encryptionMethod as unknown as number,
|
||||
});
|
||||
|
||||
expect(showUnknownKeyFormatBanner(localSyncInfo())).toBe(shouldShow);
|
||||
});
|
||||
|
||||
it('should do ppk migration', async () => {
|
||||
const { reset } = testing__setPpkMigrations_([PublicKeyAlgorithm.RsaV1, PublicKeyAlgorithm.RsaV2]);
|
||||
|
||||
|
||||
@@ -3,13 +3,15 @@ import BaseItem from '../../models/BaseItem';
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
import Setting from '../../models/Setting';
|
||||
import { MasterKeyEntity } from './types';
|
||||
import EncryptionService from './EncryptionService';
|
||||
import EncryptionService, { EncryptionMethod } from './EncryptionService';
|
||||
import { getActiveMasterKey, getActiveMasterKeyId, localSyncInfo, masterKeyEnabled, saveLocalSyncInfo, setActiveMasterKeyId, setEncryptionEnabled, SyncInfo } from '../synchronizer/syncInfoUtils';
|
||||
import JoplinError from '../../JoplinError';
|
||||
import { generateKeyPair, pkReencryptPrivateKey, ppkPasswordIsValid, shouldUpdatePpk } from './ppk/ppk';
|
||||
import KvStore from '../KvStore';
|
||||
import Folder from '../../models/Folder';
|
||||
import ShareService from '../share/ShareService';
|
||||
import { hasOwnProperty } from '@joplin/utils/object';
|
||||
import { _ } from '../../locale';
|
||||
|
||||
const logger = Logger.create('e2ee/utils');
|
||||
|
||||
@@ -135,6 +137,11 @@ export const migratePpk = async () => {
|
||||
saveLocalSyncInfo(syncInfo);
|
||||
};
|
||||
|
||||
export const isKnownEncryptionMethod = (method: EncryptionMethod|number) => {
|
||||
if (typeof method !== 'number') return false;
|
||||
return hasOwnProperty(EncryptionMethod, String(method));
|
||||
};
|
||||
|
||||
// All master keys normally should be decrypted with the master password, however
|
||||
// previously any master key could be encrypted with any password, so to support
|
||||
// this legacy case, we first check if the MK decrypts with the master password.
|
||||
@@ -202,8 +209,18 @@ export const activeMasterKeySanityCheck = () => {
|
||||
setActiveMasterKeyId(latestMasterKey.id);
|
||||
};
|
||||
|
||||
export const showUnknownKeyFormatBanner = (syncInfo: SyncInfo) => {
|
||||
for (const masterKey of syncInfo.masterKeys) {
|
||||
if (masterKeyEnabled(masterKey) && !isKnownEncryptionMethod(masterKey.encryption_method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export function showMissingMasterKeyMessage(syncInfo: SyncInfo, notLoadedMasterKeys: string[]) {
|
||||
if (!syncInfo.masterKeys.length) return false;
|
||||
if (showUnknownKeyFormatBanner(syncInfo)) return false;
|
||||
|
||||
notLoadedMasterKeys = notLoadedMasterKeys.slice();
|
||||
|
||||
@@ -341,12 +358,18 @@ export enum MasterPasswordStatus {
|
||||
NotSet = 2,
|
||||
Invalid = 3,
|
||||
Valid = 4,
|
||||
Unsupported = 5,
|
||||
}
|
||||
|
||||
export async function getMasterPasswordStatus(password: string = null): Promise<MasterPasswordStatus> {
|
||||
password = password === null ? getMasterPassword(false) : password;
|
||||
if (!password) return MasterPasswordStatus.NotSet;
|
||||
|
||||
const defaultKey = getDefaultMasterKey();
|
||||
if (defaultKey && !isKnownEncryptionMethod(defaultKey.encryption_method)) {
|
||||
return MasterPasswordStatus.Unsupported;
|
||||
}
|
||||
|
||||
const isValid = await masterPasswordIsValid(password);
|
||||
return isValid ? MasterPasswordStatus.Valid : MasterPasswordStatus.Invalid;
|
||||
}
|
||||
@@ -357,11 +380,12 @@ export async function checkHasMasterPasswordEncryptedData(syncInfo: SyncInfo = n
|
||||
}
|
||||
|
||||
const masterPasswordStatusMessages = {
|
||||
[MasterPasswordStatus.Unknown]: 'Checking...',
|
||||
[MasterPasswordStatus.Loaded]: 'Loaded',
|
||||
[MasterPasswordStatus.NotSet]: 'Not set',
|
||||
[MasterPasswordStatus.Valid]: '✓ ' + 'Valid',
|
||||
[MasterPasswordStatus.Invalid]: '❌ ' + 'Invalid',
|
||||
[MasterPasswordStatus.Unknown]: _('Checking...'),
|
||||
[MasterPasswordStatus.Loaded]: _('Loaded'),
|
||||
[MasterPasswordStatus.NotSet]: _('Not set'),
|
||||
[MasterPasswordStatus.Valid]: `✓ ${_('Valid')}`,
|
||||
[MasterPasswordStatus.Invalid]: `❌ ${_('Invalid')}`,
|
||||
[MasterPasswordStatus.Unsupported]: `❌ ${_('Unsupported key format')}`,
|
||||
};
|
||||
|
||||
export function getMasterPasswordStatusMessage(status: MasterPasswordStatus): string {
|
||||
|
||||
Reference in new issue
Block a user