mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
update pin key when the user symmetric key is set
- always set the protected pin so we can recreate pin key from user symmetric key - stop using EncryptionPair in account - use EncString for both pin key storage - update migration from old strategy on lock component
This commit is contained in:
@@ -4,7 +4,10 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import {
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
||||||
import { CsprngString } from "@bitwarden/common/types/csprng";
|
import { CsprngString } from "@bitwarden/common/types/csprng";
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ export class ElectronCryptoService extends CryptoService {
|
|||||||
super(cryptoFunctionService, encryptService, platformUtilsService, logService, stateService);
|
super(cryptoFunctionService, encryptService, platformUtilsService, logService, stateService);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async storeAdditionalKeys(key: SymmetricCryptoKey, userId?: string) {
|
protected override async storeAdditionalKeys(key: UserSymKey, userId?: string) {
|
||||||
await super.storeAdditionalKeys(key, userId);
|
await super.storeAdditionalKeys(key, userId);
|
||||||
|
|
||||||
const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId);
|
const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId);
|
||||||
|
|||||||
@@ -153,15 +153,15 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
try {
|
try {
|
||||||
const kdf = await this.stateService.getKdfType();
|
const kdf = await this.stateService.getKdfType();
|
||||||
const kdfConfig = await this.stateService.getKdfConfig();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
let oldPinProtected: EncString;
|
|
||||||
let userSymKeyPin: EncString;
|
let userSymKeyPin: EncString;
|
||||||
|
let oldPinProtected: EncString;
|
||||||
if (this.pinSet[0]) {
|
if (this.pinSet[0]) {
|
||||||
// MP on restart enabled
|
// MP on restart enabled
|
||||||
userSymKeyPin = await this.stateService.getDecryptedUserSymKeyPin();
|
userSymKeyPin = await this.stateService.getUserSymKeyPinEphemeral();
|
||||||
oldPinProtected = await this.stateService.getDecryptedPinProtected();
|
oldPinProtected = await this.stateService.getDecryptedPinProtected();
|
||||||
} else {
|
} else {
|
||||||
// MP on restart disabled
|
// MP on restart disabled
|
||||||
userSymKeyPin = new EncString(await this.stateService.getEncryptedUserSymKeyPin());
|
userSymKeyPin = await this.stateService.getUserSymKeyPin();
|
||||||
oldPinProtected = new EncString(await this.stateService.getEncryptedPinProtected());
|
oldPinProtected = new EncString(await this.stateService.getEncryptedPinProtected());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +178,12 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pinSet[0]) {
|
const protectedPin = await this.stateService.getProtectedPin();
|
||||||
const protectedPin = await this.stateService.getProtectedPin();
|
const decryptedPin = await this.cryptoService.decryptToUtf8(
|
||||||
const decryptedPin = await this.cryptoService.decryptToUtf8(
|
new EncString(protectedPin),
|
||||||
new EncString(protectedPin),
|
userSymKey
|
||||||
userSymKey
|
);
|
||||||
);
|
failed = decryptedPin !== this.pin;
|
||||||
failed = decryptedPin !== this.pin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!failed) {
|
if (!failed) {
|
||||||
await this.setKeyAndContinue(userSymKey);
|
await this.setKeyAndContinue(userSymKey);
|
||||||
@@ -317,10 +315,9 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
private async load() {
|
private async load() {
|
||||||
this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||||
|
|
||||||
let decryptedPinSet = await this.stateService.getDecryptedUserSymKeyPin();
|
let ephemeralPinSet = await this.stateService.getUserSymKeyPinEphemeral();
|
||||||
decryptedPinSet ||= await this.stateService.getDecryptedPinProtected();
|
ephemeralPinSet ||= await this.stateService.getDecryptedPinProtected();
|
||||||
// (is MP on Restart enabled && MP already entered) || (Pin is set and MP on Restart disabled)
|
this.pinLock = (this.pinSet[0] && !!ephemeralPinSet) || this.pinSet[1];
|
||||||
this.pinLock = (this.pinSet[0] && decryptedPinSet != null) || this.pinSet[1];
|
|
||||||
|
|
||||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||||
this.biometricLock =
|
this.biometricLock =
|
||||||
@@ -401,10 +398,13 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
const pinProtectedKey = await this.cryptoService.encrypt(userSymKey.key, pinKey);
|
const pinProtectedKey = await this.cryptoService.encrypt(userSymKey.key, pinKey);
|
||||||
if (masterPasswordOnRestart) {
|
if (masterPasswordOnRestart) {
|
||||||
await this.stateService.setDecryptedPinProtected(null);
|
await this.stateService.setDecryptedPinProtected(null);
|
||||||
await this.stateService.setDecryptedUserSymKeyPin(pinProtectedKey);
|
await this.stateService.setUserSymKeyPinEphemeral(pinProtectedKey);
|
||||||
} else {
|
} else {
|
||||||
await this.stateService.setEncryptedPinProtected(null);
|
await this.stateService.setEncryptedPinProtected(null);
|
||||||
await this.stateService.setEncryptedUserSymKeyPin(pinProtectedKey.encryptedString);
|
await this.stateService.setUserSymKeyPin(pinProtectedKey);
|
||||||
|
// always set the protected pin, even if MP on Restart is disabled
|
||||||
|
const encPin = await this.cryptoService.encrypt(this.pin, userSymKey);
|
||||||
|
await this.stateService.setProtectedPin(encPin.encryptedString);
|
||||||
}
|
}
|
||||||
return userSymKey;
|
return userSymKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,18 +35,20 @@ export class SetPinComponent implements OnInit {
|
|||||||
this.modalRef.close(false);
|
this.modalRef.close(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const kdf = await this.stateService.getKdfType();
|
const pinKey = await this.cryptoService.makePinKey(
|
||||||
const kdfConfig = await this.stateService.getKdfConfig();
|
this.pin,
|
||||||
const email = await this.stateService.getEmail();
|
await this.stateService.getEmail(),
|
||||||
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfConfig);
|
await this.stateService.getKdfType(),
|
||||||
|
await this.stateService.getKdfConfig()
|
||||||
|
);
|
||||||
const userKey = await this.cryptoService.getUserKeyFromMemory();
|
const userKey = await this.cryptoService.getUserKeyFromMemory();
|
||||||
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
|
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
|
||||||
|
const encPin = await this.cryptoService.encrypt(this.pin, userKey);
|
||||||
|
await this.stateService.setProtectedPin(encPin.encryptedString);
|
||||||
if (this.masterPassOnRestart) {
|
if (this.masterPassOnRestart) {
|
||||||
const encPin = await this.cryptoService.encrypt(this.pin, userKey);
|
await this.stateService.setUserSymKeyPinEphemeral(pinProtectedKey);
|
||||||
await this.stateService.setProtectedPin(encPin.encryptedString);
|
|
||||||
await this.stateService.setDecryptedUserSymKeyPin(pinProtectedKey);
|
|
||||||
} else {
|
} else {
|
||||||
await this.stateService.setEncryptedUserSymKeyPin(pinProtectedKey.encryptedString);
|
await this.stateService.setUserSymKeyPin(pinProtectedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modalRef.close(true);
|
this.modalRef.close(true);
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ export abstract class VaultTimeoutSettingsService {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getVaultTimeout: (userId?: string) => Promise<number>;
|
getVaultTimeout: (userId?: string) => Promise<number>;
|
||||||
getVaultTimeoutAction: (userId?: string) => Promise<VaultTimeoutAction>;
|
getVaultTimeoutAction: (userId?: string) => Promise<VaultTimeoutAction>;
|
||||||
|
/**
|
||||||
|
* Has the user enabled unlock with Pin.
|
||||||
|
* @returns [Pin with MP on Restart enabled, Pin without MP on Restart enabled]
|
||||||
|
*/
|
||||||
isPinLockSet: () => Promise<[boolean, boolean]>;
|
isPinLockSet: () => Promise<[boolean, boolean]>;
|
||||||
isBiometricLockSet: () => Promise<boolean>;
|
isBiometricLockSet: () => Promise<boolean>;
|
||||||
clear: (userId?: string) => Promise<void>;
|
clear: (userId?: string) => Promise<void>;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export abstract class LogInStrategy {
|
|||||||
result.forcePasswordReset = ForceResetPasswordReason.AdminForcePasswordReset;
|
result.forcePasswordReset = ForceResetPasswordReason.AdminForcePasswordReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must come before setting keys, user key needs email to update additional keys
|
||||||
await this.saveAccountInformation(response);
|
await this.saveAccountInformation(response);
|
||||||
|
|
||||||
if (response.twoFactorToken != null) {
|
if (response.twoFactorToken != null) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum KeySuffixOptions {
|
export enum KeySuffixOptions {
|
||||||
Auto = "auto",
|
Auto = "auto",
|
||||||
Biometric = "biometric",
|
Biometric = "biometric",
|
||||||
|
Pin = "pin",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export abstract class CryptoService {
|
|||||||
hasUserKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
hasUserKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
||||||
makeUserSymKey: (key: SymmetricCryptoKey) => Promise<[UserSymKey, EncString]>;
|
makeUserSymKey: (key: SymmetricCryptoKey) => Promise<[UserSymKey, EncString]>;
|
||||||
clearUserKey: (clearSecretStorage?: boolean, userId?: string) => Promise<void>;
|
clearUserKey: (clearSecretStorage?: boolean, userId?: string) => Promise<void>;
|
||||||
clearUserKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<void>;
|
|
||||||
setUserSymKeyMasterKey: (UserSymKeyMasterKey: string, userId?: string) => Promise<void>;
|
setUserSymKeyMasterKey: (UserSymKeyMasterKey: string, userId?: string) => Promise<void>;
|
||||||
setMasterKey: (key: MasterKey, userId?: string) => Promise<void>;
|
setMasterKey: (key: MasterKey, userId?: string) => Promise<void>;
|
||||||
getMasterKey: (userId?: string) => Promise<MasterKey>;
|
getMasterKey: (userId?: string) => Promise<MasterKey>;
|
||||||
|
|||||||
@@ -88,25 +88,25 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
hasUserSymKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
|
hasUserSymKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setUserSymKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
|
setUserSymKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Gets the encrypted version of the user's symmetric key encrypted by the Pin key.
|
* Gets the user's symmetric key encrypted by the Pin key.
|
||||||
* Used when Master Password on Reset is disabled
|
* Used when Master Password on Reset is disabled
|
||||||
*/
|
*/
|
||||||
getEncryptedUserSymKeyPin: (options?: StorageOptions) => Promise<string>;
|
getUserSymKeyPin: (options?: StorageOptions) => Promise<EncString>;
|
||||||
/**
|
/**
|
||||||
* Sets the encrypted version of the user's symmetric key encrypted by the Pin key.
|
* Sets the user's symmetric key encrypted by the Pin key.
|
||||||
* Used when Master Password on Reset is disabled
|
* Used when Master Password on Reset is disabled
|
||||||
*/
|
*/
|
||||||
setEncryptedUserSymKeyPin: (value: string, options?: StorageOptions) => Promise<void>;
|
setUserSymKeyPin: (value: EncString, options?: StorageOptions) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Gets the decrypted version of the user's symmetric key encrypted by the Pin key.
|
* Gets the ephemeral version of the user's symmetric key encrypted by the Pin key.
|
||||||
* Used when Master Password on Reset is enabled
|
* Used when Master Password on Reset is enabled
|
||||||
*/
|
*/
|
||||||
getDecryptedUserSymKeyPin: (options?: StorageOptions) => Promise<EncString>;
|
getUserSymKeyPinEphemeral: (options?: StorageOptions) => Promise<EncString>;
|
||||||
/**
|
/**
|
||||||
* Sets the decrypted version of the user's symmetric key encrypted by the Pin key.
|
* Sets the ephemeral version of the user's symmetric key encrypted by the Pin key.
|
||||||
* Used when Master Password on Reset is enabled
|
* Used when Master Password on Reset is enabled
|
||||||
*/
|
*/
|
||||||
setDecryptedUserSymKeyPin: (value: EncString, options?: StorageOptions) => Promise<void>;
|
setUserSymKeyPinEphemeral: (value: EncString, options?: StorageOptions) => Promise<void>;
|
||||||
|
|
||||||
// deprecated keys
|
// deprecated keys
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,16 +9,12 @@ describe("AccountSettings", () => {
|
|||||||
|
|
||||||
it("should deserialize userSymKeyPin", () => {
|
it("should deserialize userSymKeyPin", () => {
|
||||||
const accountSettings = new AccountSettings();
|
const accountSettings = new AccountSettings();
|
||||||
accountSettings.userSymKeyPin = EncryptionPair.fromJSON<string, EncString>({
|
accountSettings.userSymKeyPin = EncString.fromJSON("encrypted");
|
||||||
encrypted: "encrypted",
|
|
||||||
decrypted: "3.data",
|
|
||||||
});
|
|
||||||
const jsonObj = JSON.parse(JSON.stringify(accountSettings));
|
const jsonObj = JSON.parse(JSON.stringify(accountSettings));
|
||||||
const actual = AccountSettings.fromJSON(jsonObj);
|
const actual = AccountSettings.fromJSON(jsonObj);
|
||||||
|
|
||||||
expect(actual.userSymKeyPin).toBeInstanceOf(EncryptionPair);
|
expect(actual.userSymKeyPin).toBeInstanceOf(EncString);
|
||||||
expect(actual.userSymKeyPin.encrypted).toEqual("encrypted");
|
expect(actual.userSymKeyPin.encryptedString).toEqual("encrypted");
|
||||||
expect(actual.userSymKeyPin.decrypted.encryptedString).toEqual("3.data");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should deserialize pinProtected", () => {
|
it("should deserialize pinProtected", () => {
|
||||||
|
|||||||
@@ -235,9 +235,10 @@ export class AccountSettings {
|
|||||||
passwordGenerationOptions?: any;
|
passwordGenerationOptions?: any;
|
||||||
usernameGenerationOptions?: any;
|
usernameGenerationOptions?: any;
|
||||||
generatorOptions?: any;
|
generatorOptions?: any;
|
||||||
userSymKeyPin?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
|
userSymKeyPin?: EncString;
|
||||||
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>(); // Deprecated
|
userSymKeyPinEphemeral?: EncString;
|
||||||
protectedPin?: string;
|
protectedPin?: string;
|
||||||
|
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>(); // Deprecated
|
||||||
settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly
|
settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly
|
||||||
vaultTimeout?: number;
|
vaultTimeout?: number;
|
||||||
vaultTimeoutAction?: string = "lock";
|
vaultTimeoutAction?: string = "lock";
|
||||||
@@ -255,10 +256,7 @@ export class AccountSettings {
|
|||||||
|
|
||||||
return Object.assign(new AccountSettings(), obj, {
|
return Object.assign(new AccountSettings(), obj, {
|
||||||
environmentUrls: EnvironmentUrls.fromJSON(obj?.environmentUrls),
|
environmentUrls: EnvironmentUrls.fromJSON(obj?.environmentUrls),
|
||||||
userSymKeyPin: EncryptionPair.fromJSON<string, EncString>(
|
userSymKeyPin: EncString.fromJSON(obj.userSymKeyPin),
|
||||||
obj?.userSymKeyPin,
|
|
||||||
EncString.fromJSON
|
|
||||||
),
|
|
||||||
pinProtected: EncryptionPair.fromJSON<string, EncString>(
|
pinProtected: EncryptionPair.fromJSON<string, EncString>(
|
||||||
obj?.pinProtected,
|
obj?.pinProtected,
|
||||||
EncString.fromJSON
|
EncString.fromJSON
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
*/
|
*/
|
||||||
async setUserKey(key: UserSymKey, userId?: string): Promise<void> {
|
async setUserKey(key: UserSymKey, userId?: string): Promise<void> {
|
||||||
await this.stateService.setUserSymKey(key, { userId: userId });
|
await this.stateService.setUserSymKey(key, { userId: userId });
|
||||||
// TODO(Jake): Should we include additional keys here? When we set the memory key from storage,
|
|
||||||
// it will reset the keys in storage as well
|
|
||||||
await this.storeAdditionalKeys(key, userId);
|
await this.storeAdditionalKeys(key, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +86,10 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
* @param userId The desired user
|
* @param userId The desired user
|
||||||
* @returns The user's symmetric key
|
* @returns The user's symmetric key
|
||||||
*/
|
*/
|
||||||
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string): Promise<UserSymKey> {
|
async getUserKeyFromStorage(
|
||||||
|
keySuffix: KeySuffixOptions.Auto | KeySuffixOptions.Biometric,
|
||||||
|
userId?: string
|
||||||
|
): Promise<UserSymKey> {
|
||||||
const userKey = await this.retrieveUserKeyFromStorage(keySuffix, userId);
|
const userKey = await this.retrieveUserKeyFromStorage(keySuffix, userId);
|
||||||
if (userKey != null) {
|
if (userKey != null) {
|
||||||
if (!(await this.validateUserKey(userKey))) {
|
if (!(await this.validateUserKey(userKey))) {
|
||||||
@@ -154,27 +155,17 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the user's symmetric key
|
* Clears the user's symmetric key
|
||||||
* @param clearSecretStorage Clears all stored versions of the user keys as well,
|
* @param clearStoredKeys Clears all stored versions of the user keys as well,
|
||||||
* such as the biometrics key
|
* such as the biometrics key
|
||||||
* @param userId The desired user
|
* @param userId The desired user
|
||||||
*/
|
*/
|
||||||
async clearUserKey(clearSecretStorage = true, userId?: string): Promise<void> {
|
async clearUserKey(clearStoredKeys = true, userId?: string): Promise<void> {
|
||||||
await this.stateService.setUserSymKey(null, { userId: userId });
|
await this.stateService.setUserSymKey(null, { userId: userId });
|
||||||
if (clearSecretStorage) {
|
if (clearStoredKeys) {
|
||||||
await this.clearStoredUserKeys(userId);
|
await this.clearStoredUserKeys(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the specified version of the user's symmetric key from storage
|
|
||||||
* @param keySuffix The desired version of the user's key to clear
|
|
||||||
*/
|
|
||||||
async clearUserKeyFromStorage(keySuffix: KeySuffixOptions): Promise<void> {
|
|
||||||
keySuffix === KeySuffixOptions.Auto
|
|
||||||
? await this.stateService.setUserSymKeyAuto(null)
|
|
||||||
: await this.stateService.setUserSymKeyBiometric(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the master key encrypted user symmetric key
|
* Stores the master key encrypted user symmetric key
|
||||||
* @param userSymKeyMasterKey The master key encrypted user symmetric key to set
|
* @param userSymKeyMasterKey The master key encrypted user symmetric key to set
|
||||||
@@ -681,8 +672,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
* @param userId The desired user
|
* @param userId The desired user
|
||||||
*/
|
*/
|
||||||
async clearPinProtectedKey(userId?: string): Promise<void> {
|
async clearPinProtectedKey(userId?: string): Promise<void> {
|
||||||
|
await this.stateService.setUserSymKeyPin(null, { userId: userId });
|
||||||
await this.stateService.setEncryptedPinProtected(null, { userId: userId });
|
await this.stateService.setEncryptedPinProtected(null, { userId: userId });
|
||||||
await this.stateService.setEncryptedUserSymKeyPin(null, { userId: userId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptUserSymKeyWithPin(
|
async decryptUserSymKeyWithPin(
|
||||||
@@ -692,12 +683,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
kdfConfig: KdfConfig,
|
kdfConfig: KdfConfig,
|
||||||
pinProtectedUserSymKey?: EncString
|
pinProtectedUserSymKey?: EncString
|
||||||
): Promise<UserSymKey> {
|
): Promise<UserSymKey> {
|
||||||
if (!pinProtectedUserSymKey) {
|
pinProtectedUserSymKey ||= await this.stateService.getUserSymKeyPin();
|
||||||
const pinProtectedUserSymKeyString = await this.stateService.getEncryptedUserSymKeyPin();
|
if (pinProtectedUserSymKey) {
|
||||||
if (pinProtectedUserSymKeyString == null) {
|
throw new Error("No PIN protected key found.");
|
||||||
throw new Error("No PIN protected key found.");
|
|
||||||
}
|
|
||||||
pinProtectedUserSymKey = new EncString(pinProtectedUserSymKeyString);
|
|
||||||
}
|
}
|
||||||
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig);
|
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig);
|
||||||
const userSymKey = await this.decryptToBytes(pinProtectedUserSymKey, pinKey);
|
const userSymKey = await this.decryptToBytes(pinProtectedUserSymKey, pinKey);
|
||||||
@@ -886,30 +874,67 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async storeAdditionalKeys(key: SymmetricCryptoKey, userId?: string) {
|
/**
|
||||||
|
* Regenerates any additional keys if needed. Useful to make sure
|
||||||
|
* other keys stay in sync when the user's symmetric key has been rotated.
|
||||||
|
* @param key The user's symmetric key
|
||||||
|
* @param userId The desired user
|
||||||
|
*/
|
||||||
|
protected async storeAdditionalKeys(key: UserSymKey, userId?: string) {
|
||||||
const storeAuto = await this.shouldStoreKey(KeySuffixOptions.Auto, userId);
|
const storeAuto = await this.shouldStoreKey(KeySuffixOptions.Auto, userId);
|
||||||
|
|
||||||
if (storeAuto) {
|
if (storeAuto) {
|
||||||
await this.stateService.setUserSymKeyAuto(key.keyB64, { userId: userId });
|
await this.stateService.setUserSymKeyAuto(key.keyB64, { userId: userId });
|
||||||
} else {
|
} else {
|
||||||
await this.stateService.setUserSymKeyAuto(null, { userId: userId });
|
await this.stateService.setUserSymKeyAuto(null, { userId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId);
|
||||||
|
if (storePin) {
|
||||||
|
await this.storePinKey(key);
|
||||||
|
} else {
|
||||||
|
await this.stateService.setUserSymKeyPin(null, { userId: userId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async storePinKey(key: UserSymKey) {
|
||||||
|
const email = await this.stateService.getEmail();
|
||||||
|
const kdf = await this.stateService.getKdfType();
|
||||||
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
|
const pin = await this.decryptToUtf8(
|
||||||
|
new EncString(await this.stateService.getProtectedPin()),
|
||||||
|
key
|
||||||
|
);
|
||||||
|
const pinKey = await this.makePinKey(pin, email, kdf, kdfConfig);
|
||||||
|
await this.stateService.setUserSymKeyPin(await this.encrypt(key.key, pinKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: string) {
|
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: string) {
|
||||||
let shouldStoreKey = false;
|
let shouldStoreKey = false;
|
||||||
if (keySuffix === KeySuffixOptions.Auto) {
|
switch (keySuffix) {
|
||||||
const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId });
|
case KeySuffixOptions.Auto: {
|
||||||
shouldStoreKey = vaultTimeout == null;
|
const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId });
|
||||||
} else if (keySuffix === KeySuffixOptions.Biometric) {
|
shouldStoreKey = vaultTimeout == null;
|
||||||
const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId });
|
break;
|
||||||
shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage();
|
}
|
||||||
|
case KeySuffixOptions.Biometric: {
|
||||||
|
const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId });
|
||||||
|
shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KeySuffixOptions.Pin: {
|
||||||
|
const protectedPin = await this.stateService.getProtectedPin();
|
||||||
|
// This could cause a possible timing issue. Need to make sure the ephemeral key is set before
|
||||||
|
// we set our user key
|
||||||
|
const userSymKeyPinEphemeral = await this.stateService.getUserSymKeyPinEphemeral();
|
||||||
|
shouldStoreKey = !!protectedPin && !userSymKeyPinEphemeral;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return shouldStoreKey;
|
return shouldStoreKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async retrieveUserKeyFromStorage(
|
protected async retrieveUserKeyFromStorage(
|
||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions.Auto | KeySuffixOptions.Biometric,
|
||||||
userId?: string
|
userId?: string
|
||||||
): Promise<UserSymKey> {
|
): Promise<UserSymKey> {
|
||||||
let userKey: string;
|
let userKey: string;
|
||||||
@@ -969,6 +994,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
private async clearStoredUserKeys(userId?: string): Promise<void> {
|
private async clearStoredUserKeys(userId?: string): Promise<void> {
|
||||||
await this.stateService.setUserSymKeyAuto(null, { userId: userId });
|
await this.stateService.setUserSymKeyAuto(null, { userId: userId });
|
||||||
await this.stateService.setUserSymKeyBiometric(null, { userId: userId });
|
await this.stateService.setUserSymKeyBiometric(null, { userId: userId });
|
||||||
|
await this.stateService.setUserSymKeyPinEphemeral(null, { userId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
async makeKey(
|
async makeKey(
|
||||||
|
|||||||
@@ -720,34 +720,34 @@ export class StateService<
|
|||||||
await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options);
|
await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEncryptedUserSymKeyPin(options?: StorageOptions): Promise<string> {
|
async getUserSymKeyPin(options?: StorageOptions): Promise<EncString> {
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
)?.settings?.userSymKeyPin?.encrypted;
|
)?.settings?.userSymKeyPin;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setEncryptedUserSymKeyPin(value: string, options?: StorageOptions): Promise<void> {
|
async setUserSymKeyPin(value: EncString, options?: StorageOptions): Promise<void> {
|
||||||
const account = await this.getAccount(
|
const account = await this.getAccount(
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
);
|
);
|
||||||
account.settings.userSymKeyPin.encrypted = value;
|
account.settings.userSymKeyPin = value;
|
||||||
await this.saveAccount(
|
await this.saveAccount(
|
||||||
account,
|
account,
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDecryptedUserSymKeyPin(options?: StorageOptions): Promise<EncString> {
|
async getUserSymKeyPinEphemeral(options?: StorageOptions): Promise<EncString> {
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||||
)?.settings?.userSymKeyPin?.decrypted;
|
)?.settings?.userSymKeyPinEphemeral;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDecryptedUserSymKeyPin(value: EncString, options?: StorageOptions): Promise<void> {
|
async setUserSymKeyPinEphemeral(value: EncString, options?: StorageOptions): Promise<void> {
|
||||||
const account = await this.getAccount(
|
const account = await this.getAccount(
|
||||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||||
);
|
);
|
||||||
account.settings.userSymKeyPin.decrypted = value;
|
account.settings.userSymKeyPinEphemeral = value;
|
||||||
await this.saveAccount(
|
await this.saveAccount(
|
||||||
account,
|
account,
|
||||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||||
|
|||||||
@@ -71,8 +71,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
|
|
||||||
if (await this.keyConnectorService.getUsesKeyConnector()) {
|
if (await this.keyConnectorService.getUsesKeyConnector()) {
|
||||||
const pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
const pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||||
const pinLock =
|
|
||||||
(pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || pinSet[1];
|
let ephemeralPinSet = await this.stateService.getUserSymKeyPinEphemeral();
|
||||||
|
ephemeralPinSet ||= await this.stateService.getDecryptedPinProtected();
|
||||||
|
const pinLock = (pinSet[0] && ephemeralPinSet != null) || pinSet[1];
|
||||||
|
|
||||||
if (!pinLock && !(await this.vaultTimeoutSettingsService.isBiometricLockSet())) {
|
if (!pinLock && !(await this.vaultTimeoutSettingsService.isBiometricLockSet())) {
|
||||||
await this.logOut(userId);
|
await this.logOut(userId);
|
||||||
|
|||||||
@@ -45,11 +45,16 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
|||||||
}
|
}
|
||||||
|
|
||||||
async isPinLockSet(): Promise<[boolean, boolean]> {
|
async isPinLockSet(): Promise<[boolean, boolean]> {
|
||||||
const protectedPin = await this.stateService.getProtectedPin();
|
// we can't check the protected pin for both because old accounts only
|
||||||
let pinProtectedKey = await this.stateService.getEncryptedUserSymKeyPin();
|
// used it for MP on Restart
|
||||||
pinProtectedKey ||= await this.stateService.getEncryptedPinProtected();
|
const pinIsEnabled = !!(await this.stateService.getProtectedPin());
|
||||||
|
const aUserSymKeyPinIsSet = !!(await this.stateService.getUserSymKeyPin());
|
||||||
|
const anOldUserSymKeyPinIsSet = !!(await this.stateService.getEncryptedPinProtected());
|
||||||
|
|
||||||
return [protectedPin != null, pinProtectedKey != null];
|
return [
|
||||||
|
pinIsEnabled && !aUserSymKeyPinIsSet && !anOldUserSymKeyPinIsSet,
|
||||||
|
aUserSymKeyPinIsSet || anOldUserSymKeyPinIsSet,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async isBiometricLockSet(): Promise<boolean> {
|
async isBiometricLockSet(): Promise<boolean> {
|
||||||
@@ -106,8 +111,8 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
|||||||
|
|
||||||
async clear(userId?: string): Promise<void> {
|
async clear(userId?: string): Promise<void> {
|
||||||
await this.stateService.setEverBeenUnlocked(false, { userId: userId });
|
await this.stateService.setEverBeenUnlocked(false, { userId: userId });
|
||||||
await this.stateService.setDecryptedPinProtected(null, { userId: userId });
|
await this.stateService.setUserSymKeyPinEphemeral(null, { userId: userId });
|
||||||
await this.stateService.setDecryptedUserSymKeyPin(null, { userId: userId });
|
|
||||||
await this.stateService.setProtectedPin(null, { userId: userId });
|
await this.stateService.setProtectedPin(null, { userId: userId });
|
||||||
|
await this.stateService.setDecryptedPinProtected(null, { userId: userId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user