mirror of
https://github.com/bitwarden/jslib
synced 2025-12-26 05:03:41 +00:00
[bug] Remove keySuffix storage option and split uses into unique methods
The keySuffix options don't work with saving serialized json as a storage object - use cases simply overwrite each other in state. This commit breaks Auto and Biometric keys into distinct storage items and adjusts logic accordingly.
This commit is contained in:
@@ -65,8 +65,12 @@ export abstract class StateService {
|
||||
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
||||
setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
|
||||
getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
||||
setCryptoMasterKeyAuto: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
|
||||
getCryptoMasterKeyB64: (options: StorageOptions) => Promise<string>;
|
||||
setCryptoMasterKeyB64: (value: string, options: StorageOptions) => Promise<void>;
|
||||
getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
||||
setCryptoMasterKeyBiometric: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
|
||||
getDecodedToken: (options?: StorageOptions) => Promise<any>;
|
||||
setDecodedToken: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedCiphers: (options?: StorageOptions) => Promise<CipherView[]>;
|
||||
|
||||
@@ -34,6 +34,8 @@ export class Account {
|
||||
encryptedCiphers: { [id: string]: CipherData };
|
||||
decryptedCiphers: CipherView[];
|
||||
cryptoMasterKey: SymmetricCryptoKey;
|
||||
cryptoMasterKeyBiometric: SymmetricCryptoKey;
|
||||
cryptoMasterKeyAuto: SymmetricCryptoKey;
|
||||
cryptoMasterKeyB64: string;
|
||||
encryptedCryptoSymmetricKey: string;
|
||||
decryptedCryptoSymmetricKey: SymmetricCryptoKey;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { HtmlStorageLocation } from '../../enums/htmlStorageLocation';
|
||||
import { KeySuffixOptions } from '../../enums/keySuffixOptions';
|
||||
import { StorageLocation } from '../../enums/storageLocation';
|
||||
|
||||
export type StorageOptions = {
|
||||
keySuffix?: KeySuffixOptions;
|
||||
storageLocation?: StorageLocation;
|
||||
useSecureStorage?: boolean;
|
||||
userId?: string;
|
||||
|
||||
@@ -84,10 +84,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async getKey(keySuffix?: KeySuffixOptions, userId?: string): Promise<SymmetricCryptoKey> {
|
||||
const inMemoryKey = await this.stateService.getCryptoMasterKey(
|
||||
userId ? { userId: userId } :
|
||||
null
|
||||
);
|
||||
const inMemoryKey = await this.stateService.getCryptoMasterKey({ userId: userId });
|
||||
|
||||
if (inMemoryKey != null) {
|
||||
return inMemoryKey;
|
||||
@@ -106,7 +103,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
async getKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string): Promise<SymmetricCryptoKey> {
|
||||
const key = await this.retrieveKeyFromStorage(keySuffix, userId);
|
||||
if (key != null) {
|
||||
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer);
|
||||
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key.keyB64).buffer);
|
||||
|
||||
if (!await this.validateKey(symmetricKey)) {
|
||||
this.logService.warning('Wrong key, throwing away stored key');
|
||||
@@ -323,7 +320,11 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async hasKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {
|
||||
return await this.stateService.getCryptoMasterKeyB64({ keySuffix: keySuffix, userId: userId }) != null;
|
||||
const key = keySuffix === KeySuffixOptions.Auto ?
|
||||
await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) :
|
||||
await this.stateService.getCryptoMasterKeyBiometric({ userId: userId });
|
||||
|
||||
return key != null;
|
||||
}
|
||||
|
||||
async hasEncKey(): Promise<boolean> {
|
||||
@@ -339,7 +340,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async clearStoredKey(keySuffix: KeySuffixOptions) {
|
||||
await this.stateService.setCryptoMasterKeyB64(null, { keySuffix: keySuffix });
|
||||
keySuffix === KeySuffixOptions.Auto ?
|
||||
await this.stateService.setCryptoMasterKeyAuto(null) :
|
||||
await this.stateService.setCryptoMasterKeyBiometric(null);
|
||||
}
|
||||
|
||||
async clearKeyHash(): Promise<any> {
|
||||
@@ -706,7 +709,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) {
|
||||
return await this.stateService.getCryptoMasterKeyB64({ keySuffix: keySuffix, userId: userId });
|
||||
return keySuffix === KeySuffixOptions.Auto ?
|
||||
await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) :
|
||||
await this.stateService.getCryptoMasterKeyBiometric({ userId: userId });
|
||||
}
|
||||
|
||||
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
@@ -866,16 +871,16 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
private async clearSecretKeyStore(userId?: string): Promise<void> {
|
||||
await this.stateService.setCryptoMasterKeyB64(null, { keySuffix: KeySuffixOptions.Auto, userId: userId });
|
||||
await this.stateService.setCryptoMasterKeyB64(null, { keySuffix: KeySuffixOptions.Biometric, userId: userId });
|
||||
await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId });
|
||||
await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId });
|
||||
}
|
||||
|
||||
private async storeKey(key: SymmetricCryptoKey, userId?: string) {
|
||||
const shouldStoreAuto = await this.shouldStoreKey(KeySuffixOptions.Auto, userId);
|
||||
await this.stateService.setCryptoMasterKeyB64(shouldStoreAuto ? key.keyB64 : null, { userId: userId, keySuffix: KeySuffixOptions.Auto});
|
||||
await this.stateService.setCryptoMasterKeyAuto(shouldStoreAuto ? key : null, { userId: userId });
|
||||
|
||||
const shouldStoreBiometric = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId);
|
||||
await this.stateService.setCryptoMasterKeyB64(shouldStoreBiometric ? key.keyB64 : null, { userId: userId, keySuffix: KeySuffixOptions.Biometric });
|
||||
await this.stateService.setCryptoMasterKeyBiometric(shouldStoreBiometric ? key : null, { userId: userId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -254,34 +254,32 @@ export class StateService implements StateServiceAbstraction {
|
||||
}
|
||||
|
||||
async getCryptoMasterKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
|
||||
options = this.reconcileOptions(options, { userId: await this.getActiveUserIdFromStorage()});
|
||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.cryptoMasterKey;
|
||||
}
|
||||
|
||||
async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise<void> {
|
||||
options = this.reconcileOptions(options, { userId: await this.getActiveUserIdFromStorage()});
|
||||
const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||
account.cryptoMasterKey = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||
}
|
||||
|
||||
async getCryptoMasterKeyAuto(options?: StorageOptions): Promise<SymmetricCryptoKey> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions())))?.cryptoMasterKeyAuto;
|
||||
}
|
||||
|
||||
async setCryptoMasterKeyAuto(value: SymmetricCryptoKey, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions()));
|
||||
account.cryptoMasterKeyAuto = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, await this.defaultSecureStorageOptions()));
|
||||
}
|
||||
|
||||
async getCryptoMasterKeyB64(options: StorageOptions): Promise<string> {
|
||||
try {
|
||||
if (options?.keySuffix == null) {
|
||||
throw new RequiredSuffixError();
|
||||
}
|
||||
const value = (await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions())))?.cryptoMasterKeyB64;
|
||||
return value;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
const value = (await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions())))?.cryptoMasterKeyB64;
|
||||
return value;
|
||||
}
|
||||
|
||||
async setCryptoMasterKeyB64(value: string, options: StorageOptions): Promise<void> {
|
||||
try {
|
||||
if (value != null && options?.keySuffix == null) {
|
||||
throw new RequiredSuffixError();
|
||||
}
|
||||
const account = await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions()));
|
||||
if (account != null) {
|
||||
account.cryptoMasterKeyB64 = value;
|
||||
@@ -292,6 +290,16 @@ export class StateService implements StateServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise<SymmetricCryptoKey> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions())))?.cryptoMasterKeyBiometric;
|
||||
}
|
||||
|
||||
async setCryptoMasterKeyBiometric(value: SymmetricCryptoKey, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(this.reconcileOptions(options, await this.defaultSecureStorageOptions()));
|
||||
account.cryptoMasterKeyBiometric = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, await this.defaultSecureStorageOptions()));
|
||||
}
|
||||
|
||||
async getDecodedToken(options?: StorageOptions): Promise<any> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.decodedToken;
|
||||
}
|
||||
@@ -1332,7 +1340,6 @@ export class StateService implements StateServiceAbstraction {
|
||||
}
|
||||
requestedOptions.userId = requestedOptions?.userId ?? defaultOptions.userId;
|
||||
requestedOptions.storageLocation = requestedOptions?.storageLocation ?? defaultOptions.storageLocation;
|
||||
requestedOptions.keySuffix = requestedOptions?.keySuffix ?? defaultOptions.keySuffix;
|
||||
requestedOptions.useSecureStorage = requestedOptions?.useSecureStorage ?? defaultOptions.useSecureStorage;
|
||||
requestedOptions.htmlStorageLocation = requestedOptions?.htmlStorageLocation ?? defaultOptions.htmlStorageLocation;
|
||||
return requestedOptions;
|
||||
@@ -1382,9 +1389,3 @@ export class StateService implements StateServiceAbstraction {
|
||||
return state?.activeUserId;
|
||||
}
|
||||
}
|
||||
|
||||
class RequiredSuffixError extends Error {
|
||||
constructor(public message: string = 'The suffix option is required to get/set this key.') {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { CryptoService } from 'jslib-common/services/crypto.service';
|
||||
|
||||
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
|
||||
import { StorageLocation } from 'jslib-common/enums/storageLocation';
|
||||
|
||||
export class ElectronCryptoService extends CryptoService {
|
||||
|
||||
@@ -30,7 +31,7 @@ export class ElectronCryptoService extends CryptoService {
|
||||
*/
|
||||
private async upgradeSecurelyStoredKey() {
|
||||
// attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway.
|
||||
const key = await this.stateService.getCryptoMasterKey();
|
||||
const key = await this.stateService.getCryptoMasterKey({ storageLocation: StorageLocation.Disk, useSecureStorage: true });
|
||||
|
||||
if (key == null) {
|
||||
return;
|
||||
@@ -38,16 +39,16 @@ export class ElectronCryptoService extends CryptoService {
|
||||
|
||||
try {
|
||||
if (await this.shouldStoreKey(KeySuffixOptions.Auto)) {
|
||||
await this.stateService.getCryptoMasterKeyB64({ keySuffix: KeySuffixOptions.Auto });
|
||||
await this.stateService.setCryptoMasterKeyAuto(key);
|
||||
}
|
||||
if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) {
|
||||
await this.stateService.getCryptoMasterKey({ keySuffix: KeySuffixOptions.Biometric });
|
||||
await this.stateService.setCryptoMasterKeyBiometric(key);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(`Encountered error while upgrading obsolete Bitwarden secure storage item:`);
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
await this.stateService.setCryptoMasterKey(null);
|
||||
await this.stateService.setCryptoMasterKey(null, { storageLocation: StorageLocation.Disk, useSecureStorage: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ export class ElectronRendererSecureStorageService implements StorageService {
|
||||
const val = ipcRenderer.sendSync('keytar', {
|
||||
action: 'getPassword',
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? '',
|
||||
});
|
||||
return Promise.resolve(val != null ? JSON.parse(val) as T : null);
|
||||
}
|
||||
@@ -18,7 +17,6 @@ export class ElectronRendererSecureStorageService implements StorageService {
|
||||
const val = ipcRenderer.sendSync('keytar', {
|
||||
action: 'hasPassword',
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? '',
|
||||
});
|
||||
return Promise.resolve(!!val);
|
||||
}
|
||||
@@ -27,7 +25,6 @@ export class ElectronRendererSecureStorageService implements StorageService {
|
||||
ipcRenderer.sendSync('keytar', {
|
||||
action: 'setPassword',
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? '',
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
return Promise.resolve();
|
||||
@@ -37,7 +34,6 @@ export class ElectronRendererSecureStorageService implements StorageService {
|
||||
ipcRenderer.sendSync('keytar', {
|
||||
action: 'deletePassword',
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? '',
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user