mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
migrate pin to use user's symmetric key instead of master key
- set up new state - migrate on lock component - use new crypto service methods
This commit is contained in:
@@ -11,9 +11,10 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti
|
|||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||||
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||||
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
|
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
|
||||||
import { HashPurpose, KeySuffixOptions } from "@bitwarden/common/enums";
|
import { HashPurpose, KdfType, KeySuffixOptions } from "@bitwarden/common/enums";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -23,8 +24,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
|
import { UserSymKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
|
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
|
||||||
|
|
||||||
@@ -152,25 +153,42 @@ 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;
|
||||||
if (this.pinSet[0]) {
|
if (this.pinSet[0]) {
|
||||||
const key = await this.cryptoService.makeKeyFromPin(
|
// MP on restart enabled
|
||||||
|
userSymKeyPin = await this.stateService.getDecryptedUserSymKeyPin();
|
||||||
|
oldPinProtected = await this.stateService.getDecryptedPinProtected();
|
||||||
|
} else {
|
||||||
|
// MP on restart disabled
|
||||||
|
userSymKeyPin = new EncString(await this.stateService.getEncryptedUserSymKeyPin());
|
||||||
|
oldPinProtected = new EncString(await this.stateService.getEncryptedPinProtected());
|
||||||
|
}
|
||||||
|
|
||||||
|
let userSymKey: UserSymKey;
|
||||||
|
if (oldPinProtected) {
|
||||||
|
userSymKey = await this.decryptAndMigrateOldPinKey(true, kdf, kdfConfig, oldPinProtected);
|
||||||
|
} else {
|
||||||
|
userSymKey = await this.cryptoService.decryptUserSymKeyWithPin(
|
||||||
this.pin,
|
this.pin,
|
||||||
this.email,
|
this.email,
|
||||||
kdf,
|
kdf,
|
||||||
kdfConfig,
|
kdfConfig,
|
||||||
await this.stateService.getDecryptedPinProtected()
|
userSymKeyPin
|
||||||
);
|
);
|
||||||
const encKey = await this.cryptoService.getEncKey(key);
|
}
|
||||||
|
|
||||||
|
if (this.pinSet[0]) {
|
||||||
const protectedPin = await this.stateService.getProtectedPin();
|
const protectedPin = await this.stateService.getProtectedPin();
|
||||||
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
|
const decryptedPin = await this.cryptoService.decryptToUtf8(
|
||||||
failed = decPin !== this.pin;
|
new EncString(protectedPin),
|
||||||
if (!failed) {
|
userSymKey
|
||||||
await this.setKeyAndContinue(key);
|
);
|
||||||
}
|
failed = decryptedPin !== this.pin;
|
||||||
} else {
|
}
|
||||||
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfConfig);
|
|
||||||
failed = false;
|
if (!failed) {
|
||||||
await this.setKeyAndContinue(key);
|
await this.setKeyAndContinue(userSymKey);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
failed = true;
|
failed = true;
|
||||||
@@ -257,8 +275,9 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
await this.setKeyAndContinue(key, true);
|
await this.setKeyAndContinue(key, true);
|
||||||
}
|
}
|
||||||
private async setKeyAndContinue(key: SymmetricCryptoKey, evaluatePasswordAfterUnlock = false) {
|
|
||||||
await this.cryptoService.setKey(key);
|
private async setKeyAndContinue(key: UserSymKey, evaluatePasswordAfterUnlock = false) {
|
||||||
|
await this.cryptoService.setUserKey(key);
|
||||||
await this.doContinue(evaluatePasswordAfterUnlock);
|
await this.doContinue(evaluatePasswordAfterUnlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,9 +316,12 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private async load() {
|
private async load() {
|
||||||
this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||||
this.pinLock =
|
|
||||||
(this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) ||
|
let decryptedPinSet = await this.stateService.getDecryptedUserSymKeyPin();
|
||||||
this.pinSet[1];
|
decryptedPinSet ||= 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] && decryptedPinSet != null) || this.pinSet[1];
|
||||||
|
|
||||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||||
this.biometricLock =
|
this.biometricLock =
|
||||||
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
|
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
|
||||||
@@ -344,4 +366,46 @@ export class LockComponent implements OnInit, OnDestroy {
|
|||||||
this.enforcedMasterPasswordOptions
|
this.enforcedMasterPasswordOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates the Pin key from encrypting the user's master key to encrypting
|
||||||
|
* the user's symmetric key
|
||||||
|
* @param masterPasswordOnRestart True if Master Password on Restart is enabled
|
||||||
|
* @param kdf User's KdfType
|
||||||
|
* @param kdfConfig User's KdfConfig
|
||||||
|
* @param oldPinProtected The old Pin key from state (retrieved from different
|
||||||
|
* places depending on if Master Password on Restart was enabled)
|
||||||
|
* @returns The user's symmetric key
|
||||||
|
*/
|
||||||
|
private async decryptAndMigrateOldPinKey(
|
||||||
|
masterPasswordOnRestart: boolean,
|
||||||
|
kdf: KdfType,
|
||||||
|
kdfConfig: KdfConfig,
|
||||||
|
oldPinProtected?: EncString
|
||||||
|
): Promise<UserSymKey> {
|
||||||
|
// decrypt
|
||||||
|
const masterKey = await this.cryptoService.decryptMasterKeyWithPin(
|
||||||
|
this.pin,
|
||||||
|
this.email,
|
||||||
|
kdf,
|
||||||
|
kdfConfig,
|
||||||
|
oldPinProtected
|
||||||
|
);
|
||||||
|
const encUserSymKey = await this.stateService.getEncryptedCryptoSymmetricKey();
|
||||||
|
const userSymKey = await this.cryptoService.decryptUserSymKeyWithMasterKey(
|
||||||
|
masterKey,
|
||||||
|
new EncString(encUserSymKey)
|
||||||
|
);
|
||||||
|
// migrate
|
||||||
|
const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfConfig);
|
||||||
|
const pinProtectedKey = await this.cryptoService.encrypt(userSymKey.key, pinKey);
|
||||||
|
if (masterPasswordOnRestart) {
|
||||||
|
await this.stateService.setDecryptedPinProtected(null);
|
||||||
|
await this.stateService.setDecryptedUserSymKeyPin(pinProtectedKey);
|
||||||
|
} else {
|
||||||
|
await this.stateService.setEncryptedPinProtected(null);
|
||||||
|
await this.stateService.setEncryptedUserSymKeyPin(pinProtectedKey.encryptedString);
|
||||||
|
}
|
||||||
|
return userSymKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ export class SetPinComponent implements OnInit {
|
|||||||
const kdfConfig = await this.stateService.getKdfConfig();
|
const kdfConfig = await this.stateService.getKdfConfig();
|
||||||
const email = await this.stateService.getEmail();
|
const email = await this.stateService.getEmail();
|
||||||
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfConfig);
|
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfConfig);
|
||||||
const key = await this.cryptoService.getKey();
|
const userKey = await this.cryptoService.getUserKeyFromMemory();
|
||||||
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
|
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
|
||||||
if (this.masterPassOnRestart) {
|
if (this.masterPassOnRestart) {
|
||||||
const encPin = await this.cryptoService.encrypt(this.pin);
|
const encPin = await this.cryptoService.encrypt(this.pin, userKey);
|
||||||
await this.stateService.setProtectedPin(encPin.encryptedString);
|
await this.stateService.setProtectedPin(encPin.encryptedString);
|
||||||
await this.stateService.setDecryptedPinProtected(pinProtectedKey);
|
await this.stateService.setDecryptedUserSymKeyPin(pinProtectedKey);
|
||||||
} else {
|
} else {
|
||||||
await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
|
await this.stateService.setEncryptedUserSymKeyPin(pinProtectedKey.encryptedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modalRef.close(true);
|
this.modalRef.close(true);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export abstract class CryptoService {
|
|||||||
hasUserKey: () => Promise<boolean>;
|
hasUserKey: () => Promise<boolean>;
|
||||||
hasUserKeyInMemory: (userId?: string) => Promise<boolean>;
|
hasUserKeyInMemory: (userId?: string) => Promise<boolean>;
|
||||||
hasUserKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
hasUserKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
||||||
makeUserSymKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, 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>;
|
clearUserKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<void>;
|
||||||
setUserSymKeyMasterKey: (UserSymKeyMasterKey: string, userId?: string) => Promise<void>;
|
setUserSymKeyMasterKey: (UserSymKeyMasterKey: string, userId?: string) => Promise<void>;
|
||||||
@@ -39,7 +39,11 @@ export abstract class CryptoService {
|
|||||||
masterKey: MasterKey,
|
masterKey: MasterKey,
|
||||||
userSymKey?: UserSymKey
|
userSymKey?: UserSymKey
|
||||||
) => Promise<[UserSymKey, EncString]>;
|
) => Promise<[UserSymKey, EncString]>;
|
||||||
decryptUserSymKeyWithMasterKey: (masterKey: MasterKey, userId?: string) => Promise<UserSymKey>;
|
decryptUserSymKeyWithMasterKey: (
|
||||||
|
masterKey: MasterKey,
|
||||||
|
userSymKey?: EncString,
|
||||||
|
userId?: string
|
||||||
|
) => Promise<UserSymKey>;
|
||||||
hashPassword: (password: string, key: MasterKey, hashPurpose?: HashPurpose) => Promise<string>;
|
hashPassword: (password: string, key: MasterKey, hashPurpose?: HashPurpose) => Promise<string>;
|
||||||
setKeyHash: (keyHash: string) => Promise<void>;
|
setKeyHash: (keyHash: string) => Promise<void>;
|
||||||
getKeyHash: () => Promise<string>;
|
getKeyHash: () => Promise<string>;
|
||||||
@@ -65,6 +69,16 @@ export abstract class CryptoService {
|
|||||||
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<void[]>;
|
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<void[]>;
|
||||||
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfConfig: KdfConfig) => Promise<PinKey>;
|
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfConfig: KdfConfig) => Promise<PinKey>;
|
||||||
clearPinProtectedKey: () => Promise<void>;
|
clearPinProtectedKey: () => Promise<void>;
|
||||||
|
/**
|
||||||
|
* Decrypts the user's symmetric key with their pin
|
||||||
|
* @param pin The user's PIN
|
||||||
|
* @param salt The user's salt
|
||||||
|
* @param kdf The user's KDF
|
||||||
|
* @param kdfConfig The user's KDF config
|
||||||
|
* @param pinProtectedUserSymKey The user's PIN protected symmetric key, if not provided
|
||||||
|
* it will be retrieved from storage
|
||||||
|
* @returns The decrypted user's symmetric key
|
||||||
|
*/
|
||||||
decryptUserSymKeyWithPin: (
|
decryptUserSymKeyWithPin: (
|
||||||
pin: string,
|
pin: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
@@ -72,6 +86,16 @@ export abstract class CryptoService {
|
|||||||
kdfConfig: KdfConfig,
|
kdfConfig: KdfConfig,
|
||||||
protectedKeyCs?: EncString
|
protectedKeyCs?: EncString
|
||||||
) => Promise<UserSymKey>;
|
) => Promise<UserSymKey>;
|
||||||
|
/**
|
||||||
|
* @deprecated Left for migration purposes. Use decryptUserSymKeyWithPin instead.
|
||||||
|
*/
|
||||||
|
decryptMasterKeyWithPin: (
|
||||||
|
pin: string,
|
||||||
|
salt: string,
|
||||||
|
kdf: KdfType,
|
||||||
|
kdfConfig: KdfConfig,
|
||||||
|
protectedKeyCs?: EncString
|
||||||
|
) => Promise<MasterKey>;
|
||||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||||
clearKeys: (userId?: string) => Promise<any>;
|
clearKeys: (userId?: string) => Promise<any>;
|
||||||
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
|
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise<void>;
|
setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise<void>;
|
||||||
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
|
|
||||||
// new keys
|
|
||||||
getUserSymKey: (options?: StorageOptions) => Promise<UserSymKey>;
|
getUserSymKey: (options?: StorageOptions) => Promise<UserSymKey>;
|
||||||
setUserSymKey: (value: UserSymKey, options?: StorageOptions) => Promise<void>;
|
setUserSymKey: (value: UserSymKey, options?: StorageOptions) => Promise<void>;
|
||||||
getMasterKey: (options?: StorageOptions) => Promise<MasterKey>;
|
getMasterKey: (options?: StorageOptions) => Promise<MasterKey>;
|
||||||
@@ -89,10 +87,35 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
getUserSymKeyBiometric: (options?: StorageOptions) => Promise<string>;
|
getUserSymKeyBiometric: (options?: StorageOptions) => Promise<string>;
|
||||||
hasUserSymKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
|
hasUserSymKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setUserSymKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
|
setUserSymKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
|
||||||
// end new keys
|
/**
|
||||||
|
* Gets the encrypted version of the user's symmetric key encrypted by the Pin key.
|
||||||
|
* Used when Master Password on Reset is disabled
|
||||||
|
*/
|
||||||
|
getEncryptedUserSymKeyPin: (options?: StorageOptions) => Promise<string>;
|
||||||
|
/**
|
||||||
|
* Sets the encrypted version of the user's symmetric key encrypted by the Pin key.
|
||||||
|
* Used when Master Password on Reset is disabled
|
||||||
|
*/
|
||||||
|
setEncryptedUserSymKeyPin: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* Gets the decrypted version of the user's symmetric key encrypted by the Pin key.
|
||||||
|
* Used when Master Password on Reset is enabled
|
||||||
|
*/
|
||||||
|
getDecryptedUserSymKeyPin: (options?: StorageOptions) => Promise<EncString>;
|
||||||
|
/**
|
||||||
|
* Sets the decrypted version of the user's symmetric key encrypted by the Pin key.
|
||||||
|
* Used when Master Password on Reset is enabled
|
||||||
|
*/
|
||||||
|
setDecryptedUserSymKeyPin: (value: EncString, options?: StorageOptions) => Promise<void>;
|
||||||
|
|
||||||
// deprecated keys
|
// deprecated keys
|
||||||
|
/**
|
||||||
|
* @deprecated For migration purposes only, use getUserSymKeyMasterKey instead
|
||||||
|
*/
|
||||||
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
|
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
/**
|
||||||
|
* @deprecated For migration purposes only, use setUserSymKeyMasterKey instead
|
||||||
|
*/
|
||||||
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
|
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
||||||
setDecryptedCryptoSymmetricKey: (
|
setDecryptedCryptoSymmetricKey: (
|
||||||
@@ -128,7 +151,13 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
value: GeneratedPasswordHistory[],
|
value: GeneratedPasswordHistory[],
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* @deprecated For migration purposes only, use getDecryptedUserSymKeyPin instead
|
||||||
|
*/
|
||||||
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
|
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
|
||||||
|
/**
|
||||||
|
* @deprecated For migration purposes only, use setDecryptedUserSymKeyPin instead
|
||||||
|
*/
|
||||||
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
|
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* @deprecated Do not call this, use PolicyService
|
* @deprecated Do not call this, use PolicyService
|
||||||
@@ -255,7 +284,13 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
value: GeneratedPasswordHistory[],
|
value: GeneratedPasswordHistory[],
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* @deprecated For migration purposes only, use getEncryptedUserSymKeyPin instead
|
||||||
|
*/
|
||||||
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
|
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
|
||||||
|
/**
|
||||||
|
* @deprecated For migration purposes only, use setEncryptedUserSymKeyPin instead
|
||||||
|
*/
|
||||||
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
|
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* @deprecated Do not call this directly, use PolicyService
|
* @deprecated Do not call this directly, use PolicyService
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Utils } from "../../misc/utils";
|
|||||||
import { AccountKeys, EncryptionPair } from "./account";
|
import { AccountKeys, EncryptionPair } from "./account";
|
||||||
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
||||||
|
|
||||||
|
//TODO(Jake): Fix tests
|
||||||
describe("AccountKeys", () => {
|
describe("AccountKeys", () => {
|
||||||
describe("toJSON", () => {
|
describe("toJSON", () => {
|
||||||
it("should serialize itself", () => {
|
it("should serialize itself", () => {
|
||||||
|
|||||||
@@ -7,6 +7,20 @@ describe("AccountSettings", () => {
|
|||||||
expect(AccountSettings.fromJSON(JSON.parse("{}"))).toBeInstanceOf(AccountSettings);
|
expect(AccountSettings.fromJSON(JSON.parse("{}"))).toBeInstanceOf(AccountSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should deserialize userSymKeyPin", () => {
|
||||||
|
const accountSettings = new AccountSettings();
|
||||||
|
accountSettings.userSymKeyPin = EncryptionPair.fromJSON<string, EncString>({
|
||||||
|
encrypted: "encrypted",
|
||||||
|
decrypted: "3.data",
|
||||||
|
});
|
||||||
|
const jsonObj = JSON.parse(JSON.stringify(accountSettings));
|
||||||
|
const actual = AccountSettings.fromJSON(jsonObj);
|
||||||
|
|
||||||
|
expect(actual.userSymKeyPin).toBeInstanceOf(EncryptionPair);
|
||||||
|
expect(actual.userSymKeyPin.encrypted).toEqual("encrypted");
|
||||||
|
expect(actual.userSymKeyPin.decrypted.encryptedString).toEqual("3.data");
|
||||||
|
});
|
||||||
|
|
||||||
it("should deserialize pinProtected", () => {
|
it("should deserialize pinProtected", () => {
|
||||||
const accountSettings = new AccountSettings();
|
const accountSettings = new AccountSettings();
|
||||||
accountSettings.pinProtected = EncryptionPair.fromJSON<string, EncString>({
|
accountSettings.pinProtected = EncryptionPair.fromJSON<string, EncString>({
|
||||||
|
|||||||
@@ -235,7 +235,8 @@ export class AccountSettings {
|
|||||||
passwordGenerationOptions?: any;
|
passwordGenerationOptions?: any;
|
||||||
usernameGenerationOptions?: any;
|
usernameGenerationOptions?: any;
|
||||||
generatorOptions?: any;
|
generatorOptions?: any;
|
||||||
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
|
userSymKeyPin?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
|
||||||
|
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>(); // Deprecated
|
||||||
protectedPin?: string;
|
protectedPin?: string;
|
||||||
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;
|
||||||
@@ -254,6 +255,10 @@ 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>(
|
||||||
|
obj?.userSymKeyPin,
|
||||||
|
EncString.fromJSON
|
||||||
|
),
|
||||||
pinProtected: EncryptionPair.fromJSON<string, EncString>(
|
pinProtected: EncryptionPair.fromJSON<string, EncString>(
|
||||||
obj?.pinProtected,
|
obj?.pinProtected,
|
||||||
EncString.fromJSON
|
EncString.fromJSON
|
||||||
|
|||||||
@@ -245,28 +245,36 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
/**
|
/**
|
||||||
* Decrypts the user symmetric key with the provided master key
|
* Decrypts the user symmetric key with the provided master key
|
||||||
* @param masterKey The user's master key
|
* @param masterKey The user's master key
|
||||||
|
* @param userSymKey The user's encrypted symmetric key
|
||||||
* @param userId The desired user
|
* @param userId The desired user
|
||||||
* @returns The user's symmetric key
|
* @returns The user's symmetric key
|
||||||
*/
|
*/
|
||||||
async decryptUserSymKeyWithMasterKey(masterKey: MasterKey, userId?: string): Promise<UserSymKey> {
|
async decryptUserSymKeyWithMasterKey(
|
||||||
|
masterKey: MasterKey,
|
||||||
|
userSymKey?: EncString,
|
||||||
|
userId?: string
|
||||||
|
): Promise<UserSymKey> {
|
||||||
masterKey ||= await this.getMasterKey();
|
masterKey ||= await this.getMasterKey();
|
||||||
if (masterKey == null) {
|
if (masterKey == null) {
|
||||||
throw new Error("No Master Key found.");
|
throw new Error("No Master Key found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Jake): Do we need to let this be passed in as well?
|
if (!userSymKey) {
|
||||||
const userSymKeyMasterKey = await this.stateService.getUserSymKeyMasterKey({ userId: userId });
|
const userSymKeyMasterKey = await this.stateService.getUserSymKeyMasterKey({
|
||||||
if (userSymKeyMasterKey == null) {
|
userId: userId,
|
||||||
throw new Error("No User Key found.");
|
});
|
||||||
|
if (userSymKeyMasterKey == null) {
|
||||||
|
throw new Error("No User Key found.");
|
||||||
|
}
|
||||||
|
userSymKey = new EncString(userSymKeyMasterKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
let decUserKey: ArrayBuffer;
|
let decUserKey: ArrayBuffer;
|
||||||
const encUserKey = new EncString(userSymKeyMasterKey);
|
if (userSymKey.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||||
if (encUserKey.encryptionType === EncryptionType.AesCbc256_B64) {
|
decUserKey = await this.decryptToBytes(userSymKey, masterKey);
|
||||||
decUserKey = await this.decryptToBytes(encUserKey, masterKey);
|
} else if (userSymKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) {
|
||||||
} else if (encUserKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) {
|
|
||||||
const newKey = await this.stretchKey(masterKey);
|
const newKey = await this.stretchKey(masterKey);
|
||||||
decUserKey = await this.decryptToBytes(encUserKey, newKey);
|
decUserKey = await this.decryptToBytes(userSymKey, newKey);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unsupported encryption type.");
|
throw new Error("Unsupported encryption type.");
|
||||||
}
|
}
|
||||||
@@ -673,28 +681,19 @@ 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> {
|
||||||
return await this.stateService.setEncryptedPinProtected(null, { userId: userId });
|
await this.stateService.setEncryptedPinProtected(null, { userId: userId });
|
||||||
|
await this.stateService.setEncryptedUserSymKeyPin(null, { userId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the user's symmetric key with their pin
|
|
||||||
* @param pin The user's PIN
|
|
||||||
* @param salt The user's salt
|
|
||||||
* @param kdf The user's KDF
|
|
||||||
* @param kdfConfig The user's KDF config
|
|
||||||
* @param pinProtectedUserSymKey The user's PIN protected symmetric key, if not provided
|
|
||||||
* it will be retrieved from storage
|
|
||||||
* @returns The decrypted user's symmetric key
|
|
||||||
*/
|
|
||||||
async decryptUserSymKeyWithPin(
|
async decryptUserSymKeyWithPin(
|
||||||
pin: string,
|
pin: string,
|
||||||
salt: string,
|
salt: string,
|
||||||
kdf: KdfType,
|
kdf: KdfType,
|
||||||
kdfConfig: KdfConfig,
|
kdfConfig: KdfConfig,
|
||||||
pinProtectedUserSymKey: EncString = null
|
pinProtectedUserSymKey?: EncString
|
||||||
): Promise<UserSymKey> {
|
): Promise<UserSymKey> {
|
||||||
if (pinProtectedUserSymKey == null) {
|
if (!pinProtectedUserSymKey) {
|
||||||
const pinProtectedUserSymKeyString = await this.stateService.getEncryptedPinProtected();
|
const pinProtectedUserSymKeyString = await this.stateService.getEncryptedUserSymKeyPin();
|
||||||
if (pinProtectedUserSymKeyString == null) {
|
if (pinProtectedUserSymKeyString == null) {
|
||||||
throw new Error("No PIN protected key found.");
|
throw new Error("No PIN protected key found.");
|
||||||
}
|
}
|
||||||
@@ -705,6 +704,25 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
return new SymmetricCryptoKey(userSymKey) as UserSymKey;
|
return new SymmetricCryptoKey(userSymKey) as UserSymKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async decryptMasterKeyWithPin(
|
||||||
|
pin: string,
|
||||||
|
salt: string,
|
||||||
|
kdf: KdfType,
|
||||||
|
kdfConfig: KdfConfig,
|
||||||
|
pinProtectedMasterKey?: EncString
|
||||||
|
): Promise<MasterKey> {
|
||||||
|
if (!pinProtectedMasterKey) {
|
||||||
|
const pinProtectedMasterKeyString = await this.stateService.getEncryptedPinProtected();
|
||||||
|
if (pinProtectedMasterKeyString == null) {
|
||||||
|
throw new Error("No PIN protected key found.");
|
||||||
|
}
|
||||||
|
pinProtectedMasterKey = new EncString(pinProtectedMasterKeyString);
|
||||||
|
}
|
||||||
|
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig);
|
||||||
|
const masterKey = await this.decryptToBytes(pinProtectedMasterKey, pinKey);
|
||||||
|
return new SymmetricCryptoKey(masterKey) as MasterKey;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param keyMaterial The key material to derive the send key from
|
* @param keyMaterial The key material to derive the send key from
|
||||||
* @returns A new send key
|
* @returns A new send key
|
||||||
|
|||||||
@@ -619,7 +619,7 @@ export class StateService<
|
|||||||
* so we can unlock with MP offline
|
* so we can unlock with MP offline
|
||||||
*/
|
*/
|
||||||
async getUserSymKeyMasterKey(options?: StorageOptions): Promise<string> {
|
async getUserSymKeyMasterKey(options?: StorageOptions): Promise<string> {
|
||||||
// TODO: defaultOnDiskOptions? Other's are saved in secure storage
|
// TODO(Jake): defaultOnDiskOptions? Other's are saved in secure storage
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
)?.keys.userSymKeyMasterKey;
|
)?.keys.userSymKeyMasterKey;
|
||||||
@@ -630,7 +630,7 @@ export class StateService<
|
|||||||
* so we can unlock with MP offline
|
* so we can unlock with MP offline
|
||||||
*/
|
*/
|
||||||
async setUserSymKeyMasterKey(value: string, options?: StorageOptions): Promise<void> {
|
async setUserSymKeyMasterKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
// TODO: defaultOnDiskOptions? Other's are saved in secure storage
|
// TODO(Jake): defaultOnDiskOptions? Other's are saved in secure storage
|
||||||
const account = await this.getAccount(
|
const account = await this.getAccount(
|
||||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
);
|
);
|
||||||
@@ -720,6 +720,40 @@ export class StateService<
|
|||||||
await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options);
|
await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEncryptedUserSymKeyPin(options?: StorageOptions): Promise<string> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.settings?.userSymKeyPin?.encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setEncryptedUserSymKeyPin(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.settings.userSymKeyPin.encrypted = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDecryptedUserSymKeyPin(options?: StorageOptions): Promise<EncString> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||||
|
)?.settings?.userSymKeyPin?.decrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDecryptedUserSymKeyPin(value: EncString, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||||
|
);
|
||||||
|
account.settings.userSymKeyPin.decrypted = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use UserSymKeyAuto instead
|
* @deprecated Use UserSymKeyAuto instead
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
|||||||
|
|
||||||
async isPinLockSet(): Promise<[boolean, boolean]> {
|
async isPinLockSet(): Promise<[boolean, boolean]> {
|
||||||
const protectedPin = await this.stateService.getProtectedPin();
|
const protectedPin = await this.stateService.getProtectedPin();
|
||||||
const pinProtectedKey = await this.stateService.getEncryptedPinProtected();
|
let pinProtectedKey = await this.stateService.getEncryptedUserSymKeyPin();
|
||||||
|
pinProtectedKey ||= await this.stateService.getEncryptedPinProtected();
|
||||||
|
|
||||||
return [protectedPin != null, pinProtectedKey != null];
|
return [protectedPin != null, pinProtectedKey != null];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +107,7 @@ 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.setDecryptedPinProtected(null, { userId: userId });
|
||||||
|
await this.stateService.setDecryptedUserSymKeyPin(null, { userId: userId });
|
||||||
await this.stateService.setProtectedPin(null, { userId: userId });
|
await this.stateService.setProtectedPin(null, { userId: userId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user