1
0
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:
Jacob Fink
2023-06-06 12:48:56 -04:00
parent 91ac281da0
commit 7837202180
10 changed files with 255 additions and 57 deletions

View File

@@ -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;
}
} }

View File

@@ -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);

View File

@@ -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>;

View File

@@ -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

View File

@@ -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", () => {

View File

@@ -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>({

View File

@@ -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

View File

@@ -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

View File

@@ -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
*/ */

View File

@@ -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 });
} }
} }