mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 19:23:52 +00:00
[PM-21147] User key transferred over ipc within desktop app without its prototype (#15047)
* user key transferred over ipc within desktop app without its prototype. `UserKey` object was transferred over IPC as regular `Object` type and not recreated as `SymmetricCryptoKey` type, losing its original functions and properties. As a result `inner` method did not exist and user key silently failed during decryption of encrypted client key halves during biometric unlock. * ipc biometrics serializable user key type * use encrypt service directly for decryption * moving electron key service to KM * log error when unlock via biometrics fails with exception in lock component * bring back tech debt comment * lock component logging prefix
This commit is contained in:
124
apps/desktop/src/key-management/electron-key.service.ts
Normal file
124
apps/desktop/src/key-management/electron-key.service.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { CsprngString } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey } from "@bitwarden/common/types/key";
|
||||
import {
|
||||
KdfConfigService,
|
||||
DefaultKeyService,
|
||||
BiometricStateService,
|
||||
} from "@bitwarden/key-management";
|
||||
|
||||
import { DesktopBiometricsService } from "./biometrics/desktop.biometrics.service";
|
||||
|
||||
// TODO Remove this class once biometric client key half storage is moved https://bitwarden.atlassian.net/browse/PM-22342
|
||||
export class ElectronKeyService extends DefaultKeyService {
|
||||
constructor(
|
||||
pinService: PinServiceAbstraction,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
encryptService: EncryptService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
accountService: AccountService,
|
||||
stateProvider: StateProvider,
|
||||
private biometricStateService: BiometricStateService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
private biometricService: DesktopBiometricsService,
|
||||
) {
|
||||
super(
|
||||
pinService,
|
||||
masterPasswordService,
|
||||
keyGenerationService,
|
||||
cryptoFunctionService,
|
||||
encryptService,
|
||||
platformUtilsService,
|
||||
logService,
|
||||
stateService,
|
||||
accountService,
|
||||
stateProvider,
|
||||
kdfConfigService,
|
||||
);
|
||||
}
|
||||
|
||||
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
|
||||
return super.hasUserKeyStored(keySuffix, userId);
|
||||
}
|
||||
|
||||
override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<void> {
|
||||
await super.clearStoredUserKey(keySuffix, userId);
|
||||
}
|
||||
|
||||
protected override async storeAdditionalKeys(key: UserKey, userId: UserId) {
|
||||
await super.storeAdditionalKeys(key, userId);
|
||||
|
||||
if (await this.biometricStateService.getBiometricUnlockEnabled(userId)) {
|
||||
await this.storeBiometricsProtectedUserKey(key, userId);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async getKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey | null> {
|
||||
return await super.getKeyFromStorage(keySuffix, userId);
|
||||
}
|
||||
|
||||
private async storeBiometricsProtectedUserKey(userKey: UserKey, userId: UserId): Promise<void> {
|
||||
// May resolve to null, in which case no client key have is required
|
||||
const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId);
|
||||
await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf);
|
||||
await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64);
|
||||
}
|
||||
|
||||
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId: UserId): Promise<boolean> {
|
||||
return await super.shouldStoreKey(keySuffix, userId);
|
||||
}
|
||||
|
||||
protected override async clearAllStoredUserKeys(userId: UserId): Promise<void> {
|
||||
await this.biometricService.deleteBiometricUnlockKeyForUser(userId);
|
||||
await super.clearAllStoredUserKeys(userId);
|
||||
}
|
||||
|
||||
private async getBiometricEncryptionClientKeyHalf(
|
||||
userKey: UserKey,
|
||||
userId: UserId,
|
||||
): Promise<CsprngString | null> {
|
||||
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
||||
if (!requireClientKeyHalf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Retrieve existing key half if it exists
|
||||
let clientKeyHalf: CsprngString | null = null;
|
||||
const encryptedClientKeyHalf =
|
||||
await this.biometricStateService.getEncryptedClientKeyHalf(userId);
|
||||
if (encryptedClientKeyHalf != null) {
|
||||
clientKeyHalf = (await this.encryptService.decryptString(
|
||||
encryptedClientKeyHalf,
|
||||
userKey,
|
||||
)) as CsprngString;
|
||||
}
|
||||
if (clientKeyHalf == null) {
|
||||
// Set a key half if it doesn't exist
|
||||
const keyBytes = await this.cryptoFunctionService.randomBytes(32);
|
||||
clientKeyHalf = Utils.fromBufferToUtf8(keyBytes) as CsprngString;
|
||||
const encKey = await this.encryptService.encryptString(clientKeyHalf, userKey);
|
||||
await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId);
|
||||
}
|
||||
|
||||
return clientKeyHalf;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user