mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
fix EncString serialization issues & various fixes
Co-authored-by: Matt Gibson <MGibson1@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId });
|
if (await this.stateService.hasCryptoMasterKeyBiometric({ userId })) {
|
||||||
if (oldBiometricKey) {
|
const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId });
|
||||||
// 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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user