1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 00:33:44 +00:00

fix EncString serialization issues & various fixes

Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
This commit is contained in:
Jacob Fink
2023-06-13 15:55:59 -04:00
parent 012de1b92f
commit 7110e3cda6
9 changed files with 47 additions and 39 deletions

View File

@@ -10,7 +10,10 @@ 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 {
MasterKey,
SymmetricCryptoKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BrowserApi } from "../platform/browser/browser-api"; import { BrowserApi } from "../platform/browser/browser-api";
@@ -320,8 +323,8 @@ export class NativeMessagingBackground {
} }
if (message.response === "unlocked") { if (message.response === "unlocked") {
await this.cryptoService.setKey( await this.cryptoService.setMasterKey(
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer) new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer) as MasterKey
); );
// Verify key is correct by attempting to decrypt a secret // Verify key is correct by attempting to decrypt a secret
@@ -329,7 +332,7 @@ export class NativeMessagingBackground {
await this.cryptoService.getFingerprint(await this.stateService.getUserId()); await this.cryptoService.getFingerprint(await this.stateService.getUserId());
} catch (e) { } catch (e) {
this.logService.error("Unable to verify key: " + e); this.logService.error("Unable to verify key: " + e);
await this.cryptoService.clearKey(); await this.cryptoService.clearKeys();
this.showWrongUserDialog(); this.showWrongUserDialog();
// Exit early // Exit early

View File

@@ -3,11 +3,11 @@ import { CryptoService } from "@bitwarden/common/platform/services/crypto.servic
export class BrowserCryptoService extends CryptoService { export class BrowserCryptoService extends CryptoService {
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) {
if (keySuffix === "biometric") { if (keySuffix === KeySuffixOptions.Biometric) {
await this.platformUtilService.authenticateBiometric(); await this.platformUtilService.authenticateBiometric();
return (await this.getKey())?.keyB64; return (await this.getUserKeyFromMemory())?.keyB64;
} }
return await super.retrieveKeyFromStorage(keySuffix); return await super.retrieveUserKeyFromStorage(keySuffix);
} }
} }

View File

@@ -42,16 +42,12 @@ export class ElectronCryptoService extends CryptoService {
keySuffix: KeySuffixOptions, keySuffix: KeySuffixOptions,
userId?: string userId?: string
): Promise<UserSymKey> { ): Promise<UserSymKey> {
const userKey = super.retrieveUserKeyFromStorage(keySuffix, userId);
if (userKey) {
return userKey;
}
if (keySuffix === KeySuffixOptions.Biometric) { if (keySuffix === KeySuffixOptions.Biometric) {
await this.migrateBiometricKeyIfNeeded(userId); await this.migrateBiometricKeyIfNeeded(userId);
const userKey = await this.stateService.getUserSymKeyBiometric({ userId: userId }); const userKey = await this.stateService.getUserSymKeyBiometric({ userId: userId });
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey).buffer) as UserSymKey; return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey).buffer) as UserSymKey;
} }
return null; return await super.retrieveUserKeyFromStorage(keySuffix, userId);
} }
protected async storeBiometricKey(key: UserSymKey, userId?: string): Promise<void> { protected async storeBiometricKey(key: UserSymKey, userId?: string): Promise<void> {
@@ -86,15 +82,18 @@ export class ElectronCryptoService extends CryptoService {
} }
private async migrateBiometricKeyIfNeeded(userId?: string) { private async migrateBiometricKeyIfNeeded(userId?: string) {
if (await this.stateService.hasCryptoMasterKeyBiometric({ userId })) {
const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId }); const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId });
if (oldBiometricKey) {
// decrypt // decrypt
const masterKey = new SymmetricCryptoKey( const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldBiometricKey)) as MasterKey;
Utils.fromB64ToArray(oldBiometricKey).buffer let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
) as MasterKey; encUserKey = encUserKey ?? (await this.stateService.getUserSymKeyMasterKey());
if (!encUserKey) {
throw new Error("No user key found during biometric migration");
}
const userSymKey = await this.decryptUserSymKeyWithMasterKey( const userSymKey = await this.decryptUserSymKeyWithMasterKey(
masterKey, masterKey,
new EncString(await this.stateService.getEncryptedCryptoSymmetricKey()) new EncString(encUserKey)
); );
// migrate // migrate
await this.storeBiometricKey(userSymKey, userId); await this.storeBiometricKey(userSymKey, userId);

View File

@@ -8,7 +8,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.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 { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { StateService } from "@bitwarden/common/platform/services/state.service"; import { StateService } from "@bitwarden/common/platform/services/state.service";
@@ -144,7 +144,9 @@ export class NativeMessageHandlerService {
} }
private async handleEncryptedMessage(message: EncryptedMessage) { private async handleEncryptedMessage(message: EncryptedMessage) {
message.encryptedCommand = EncString.fromJSON(message.encryptedCommand.toString()); message.encryptedCommand = EncString.fromJSON(
message.encryptedCommand.toString() as EncryptedString
);
const decryptedCommandData = await this.decryptPayload(message); const decryptedCommandData = await this.decryptPayload(message);
const { command } = decryptedCommandData; const { command } = decryptedCommandData;

View File

@@ -162,7 +162,8 @@ export class LockComponent implements OnInit, OnDestroy {
} else { } else {
// MP on restart disabled // MP on restart disabled
userSymKeyPin = await this.stateService.getUserSymKeyPin(); userSymKeyPin = await this.stateService.getUserSymKeyPin();
oldPinProtected = new EncString(await this.stateService.getEncryptedPinProtected()); const oldEncryptedKey = await this.stateService.getEncryptedPinProtected();
oldPinProtected = oldEncryptedKey ? new EncString(oldEncryptedKey) : undefined;
} }
let userSymKey: UserSymKey; let userSymKey: UserSymKey;

View File

@@ -235,8 +235,8 @@ export class AccountSettings {
passwordGenerationOptions?: any; passwordGenerationOptions?: any;
usernameGenerationOptions?: any; usernameGenerationOptions?: any;
generatorOptions?: any; generatorOptions?: any;
userSymKeyPin?: EncString; userSymKeyPin?: EncryptedString;
userSymKeyPinEphemeral?: EncString; userSymKeyPinEphemeral?: EncryptedString;
protectedPin?: string; protectedPin?: string;
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>(); // Deprecated 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
@@ -256,7 +256,6 @@ 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: EncString.fromJSON(obj.userSymKeyPin),
pinProtected: EncryptionPair.fromJSON<string, EncString>( pinProtected: EncryptionPair.fromJSON<string, EncString>(
obj?.pinProtected, obj?.pinProtected,
EncString.fromJSON EncString.fromJSON

View File

@@ -1,4 +1,4 @@
import { Jsonify } from "type-fest"; import { Jsonify, Opaque } from "type-fest";
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../enums"; import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../enums";
import { Utils } from "../../../platform/misc/utils"; import { Utils } from "../../../platform/misc/utils";
@@ -7,7 +7,7 @@ import { Encrypted } from "../../interfaces/encrypted";
import { SymmetricCryptoKey } from "./symmetric-crypto-key"; import { SymmetricCryptoKey } from "./symmetric-crypto-key";
export class EncString implements Encrypted { export class EncString implements Encrypted {
encryptedString?: string; encryptedString?: EncryptedString;
encryptionType?: EncryptionType; encryptionType?: EncryptionType;
decryptedValue?: string; decryptedValue?: string;
data?: string; data?: string;
@@ -53,14 +53,14 @@ export class EncString implements Encrypted {
private initFromData(encType: EncryptionType, data: string, iv: string, mac: string) { private initFromData(encType: EncryptionType, data: string, iv: string, mac: string) {
if (iv != null) { if (iv != null) {
this.encryptedString = encType + "." + iv + "|" + data; this.encryptedString = (encType + "." + iv + "|" + data) as EncryptedString;
} else { } else {
this.encryptedString = encType + "." + data; this.encryptedString = (encType + "." + data) as EncryptedString;
} }
// mac // mac
if (mac != null) { if (mac != null) {
this.encryptedString += "|" + mac; this.encryptedString = (this.encryptedString + "|" + mac) as EncryptedString;
} }
this.encryptionType = encType; this.encryptionType = encType;
@@ -70,7 +70,7 @@ export class EncString implements Encrypted {
} }
private initFromEncryptedString(encryptedString: string) { private initFromEncryptedString(encryptedString: string) {
this.encryptedString = encryptedString as string; this.encryptedString = encryptedString as EncryptedString;
if (!this.encryptedString) { if (!this.encryptedString) {
return; return;
} }
@@ -165,3 +165,5 @@ export class EncString implements Encrypted {
: await cryptoService.getKeyForUserEncryption(); : await cryptoService.getKeyForUserEncryption();
} }
} }
export type EncryptedString = Opaque<string, "EncString">;

View File

@@ -506,7 +506,7 @@ export class CryptoService implements CryptoServiceAbstraction {
pinProtectedUserSymKey?: EncString pinProtectedUserSymKey?: EncString
): Promise<UserSymKey> { ): Promise<UserSymKey> {
pinProtectedUserSymKey ||= await this.stateService.getUserSymKeyPin(); pinProtectedUserSymKey ||= await this.stateService.getUserSymKeyPin();
if (pinProtectedUserSymKey) { if (!pinProtectedUserSymKey) {
throw new Error("No PIN protected key found."); throw new Error("No PIN protected key found.");
} }
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig); const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig);

View File

@@ -715,16 +715,17 @@ export class StateService<
} }
async getUserSymKeyPin(options?: StorageOptions): Promise<EncString> { async getUserSymKeyPin(options?: StorageOptions): Promise<EncString> {
return ( return EncString.fromJSON(
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
)?.settings?.userSymKeyPin; ?.settings?.userSymKeyPin
);
} }
async setUserSymKeyPin(value: EncString, 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 = value; account.settings.userSymKeyPin = value?.encryptedString;
await this.saveAccount( await this.saveAccount(
account, account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()) this.reconcileOptions(options, await this.defaultOnDiskOptions())
@@ -732,16 +733,17 @@ export class StateService<
} }
async getUserSymKeyPinEphemeral(options?: StorageOptions): Promise<EncString> { async getUserSymKeyPinEphemeral(options?: StorageOptions): Promise<EncString> {
return ( return EncString.fromJSON(
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) (await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())))
)?.settings?.userSymKeyPinEphemeral; ?.settings?.userSymKeyPinEphemeral
);
} }
async setUserSymKeyPinEphemeral(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.userSymKeyPinEphemeral = value; account.settings.userSymKeyPinEphemeral = value?.encryptedString;
await this.saveAccount( await this.saveAccount(
account, account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())