diff --git a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts index 6415443bfbc..97e1d322a0e 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -1,3 +1,4 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { BiometricsService } from "@bitwarden/key-management"; @@ -6,10 +7,10 @@ import { BiometricsService } from "@bitwarden/key-management"; * specifically for the main process. */ export abstract class DesktopBiometricsService extends BiometricsService { - abstract setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise; + abstract setBiometricProtectedUnlockKeyForUser( + userId: UserId, + value: SymmetricCryptoKey, + ): Promise; abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise; - abstract setupBiometrics(): Promise; - - abstract setClientKeyHalfForUser(userId: UserId, value: string | null): Promise; } diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts index fe40aad54d9..e270c4cc50f 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts @@ -1,5 +1,6 @@ import { ipcMain } from "electron"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -37,17 +38,12 @@ export class MainBiometricsIPCListener { } return await this.biometricService.setBiometricProtectedUnlockKeyForUser( message.userId as UserId, - message.key, + SymmetricCryptoKey.fromString(message.key), ); case BiometricAction.RemoveKeyForUser: return await this.biometricService.deleteBiometricUnlockKeyForUser( message.userId as UserId, ); - case BiometricAction.SetClientKeyHalf: - return await this.biometricService.setClientKeyHalfForUser( - message.userId as UserId, - message.key, - ); case BiometricAction.Setup: return await this.biometricService.setupBiometrics(); diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts index 09a4dcef4b3..d2c29f4d291 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts @@ -1,10 +1,12 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { BiometricsService, @@ -13,6 +15,7 @@ import { } from "@bitwarden/key-management"; import { WindowMain } from "../../main/window.main"; +import { MainCryptoFunctionService } from "../../platform/main/main-crypto-function.service"; import { MainBiometricsService } from "./main-biometrics.service"; import OsBiometricsServiceLinux from "./os-biometrics-linux.service"; @@ -27,12 +30,16 @@ jest.mock("@bitwarden/desktop-napi", () => { }; }); +const unlockKey = new SymmetricCryptoKey(new Uint8Array(64)); + describe("MainBiometricsService", function () { const i18nService = mock(); const windowMain = mock(); const logService = mock(); const messagingService = mock(); const biometricStateService = mock(); + const cryptoFunctionService = mock(); + const encryptService = mock(); it("Should call the platformspecific methods", async () => { const sut = new MainBiometricsService( @@ -42,6 +49,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); const mockService = mock(); @@ -60,6 +69,8 @@ describe("MainBiometricsService", function () { messagingService, "win32", biometricStateService, + encryptService, + cryptoFunctionService, ); const internalService = (sut as any).osBiometricsService; @@ -75,6 +86,8 @@ describe("MainBiometricsService", function () { messagingService, "darwin", biometricStateService, + encryptService, + cryptoFunctionService, ); const internalService = (sut as any).osBiometricsService; expect(internalService).not.toBeNull(); @@ -89,6 +102,8 @@ describe("MainBiometricsService", function () { messagingService, "linux", biometricStateService, + encryptService, + cryptoFunctionService, ); const internalService = (sut as any).osBiometricsService; @@ -109,6 +124,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); innerService = mock(); @@ -196,6 +213,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); const osBiometricsService = mock(); (sut as any).osBiometricsService = osBiometricsService; @@ -206,40 +225,6 @@ describe("MainBiometricsService", function () { }); }); - describe("setClientKeyHalfForUser", () => { - let sut: MainBiometricsService; - - beforeEach(() => { - sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - messagingService, - process.platform, - biometricStateService, - ); - }); - - it("should set the client key half for the user", async () => { - const userId = "test" as UserId; - const keyHalf = "testKeyHalf"; - - await sut.setClientKeyHalfForUser(userId, keyHalf); - - expect((sut as any).clientKeyHalves.has(userId)).toBe(true); - expect((sut as any).clientKeyHalves.get(userId)).toBe(keyHalf); - }); - - it("should reset the client key half for the user", async () => { - const userId = "test" as UserId; - - await sut.setClientKeyHalfForUser(userId, null); - - expect((sut as any).clientKeyHalves.has(userId)).toBe(true); - expect((sut as any).clientKeyHalves.get(userId)).toBe(null); - }); - }); - describe("authenticateWithBiometrics", () => { it("should call the platform specific authenticate method", async () => { const sut = new MainBiometricsService( @@ -249,6 +234,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); const osBiometricsService = mock(); (sut as any).osBiometricsService = osBiometricsService; @@ -271,6 +258,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); osBiometricsService = mock(); (sut as any).osBiometricsService = osBiometricsService; @@ -321,6 +310,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); osBiometricsService = mock(); (sut as any).osBiometricsService = osBiometricsService; @@ -328,8 +319,6 @@ describe("MainBiometricsService", function () { it("should throw an error if no client key half is provided", async () => { const userId = "test" as UserId; - const unlockKey = "testUnlockKey"; - await expect(sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey)).rejects.toThrow( "No client key half provided for user", ); @@ -337,8 +326,6 @@ describe("MainBiometricsService", function () { it("should call the platform specific setBiometricKey method", async () => { const userId = "test" as UserId; - const unlockKey = "testUnlockKey"; - (sut as any).clientKeyHalves.set(userId, "testKeyHalf"); await sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey); @@ -361,6 +348,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); const osBiometricsService = mock(); (sut as any).osBiometricsService = osBiometricsService; @@ -387,6 +376,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); }); @@ -416,6 +407,8 @@ describe("MainBiometricsService", function () { messagingService, process.platform, biometricStateService, + encryptService, + cryptoFunctionService, ); const shouldAutoPrompt = await sut.getShouldAutopromptNow(); diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts index cf80fa5f7f3..1c157b1a7b6 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -1,6 +1,9 @@ +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; @@ -23,6 +26,8 @@ export class MainBiometricsService extends DesktopBiometricsService { private messagingService: MessagingService, private platform: NodeJS.Platform, private biometricStateService: BiometricStateService, + private encryptService: EncryptService, + private cryptoFunctionService: CryptoFunctionService, ) { super(); if (platform === "win32") { @@ -104,10 +109,6 @@ export class MainBiometricsService extends DesktopBiometricsService { return await this.osBiometricsService.osBiometricsSetup(); } - async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise { - this.clientKeyHalves.set(userId, value); - } - async authenticateWithBiometrics(): Promise { return await this.osBiometricsService.authenticateBiometric(); } @@ -125,17 +126,25 @@ export class MainBiometricsService extends DesktopBiometricsService { return SymmetricCryptoKey.fromString(biometricKey) as UserKey; } - async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { + async setBiometricProtectedUnlockKeyForUser( + userId: UserId, + key: SymmetricCryptoKey, + ): Promise { const service = "Bitwarden_biometric"; const storageKey = `${userId}_user_biometric`; if (!this.clientKeyHalves.has(userId)) { - throw new Error("No client key half provided for user"); + const clientKeyHalf = await this.getOrCreateBiometricEncryptionClientKeyHalf(key, userId); + if (clientKeyHalf == null) { + throw new Error("Client key half is required for biometric unlock but not set."); + } else { + this.clientKeyHalves.set(userId, Utils.fromBufferToB64(clientKeyHalf)); + } } return await this.osBiometricsService.setBiometricKey( service, storageKey, - value, + key.toBase64(), this.clientKeyHalves.get(userId) ?? undefined, ); } @@ -167,4 +176,30 @@ export class MainBiometricsService extends DesktopBiometricsService { async canEnableBiometricUnlock(): Promise { return true; } + + private async getOrCreateBiometricEncryptionClientKeyHalf( + key: SymmetricCryptoKey, + userId: UserId, + ): Promise { + const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); + if (!requireClientKeyHalf) { + return null; + } + + // Retrieve existing key half if it exists + let clientKeyHalf: Uint8Array | null = null; + const encryptedClientKeyHalf = + await this.biometricStateService.getEncryptedClientKeyHalf(userId); + if (encryptedClientKeyHalf != null) { + clientKeyHalf = await this.encryptService.decryptBytes(encryptedClientKeyHalf, key); + } + if (clientKeyHalf == null) { + // Set a key half if it doesn't exist + const keyBytes = await this.cryptoFunctionService.randomBytes(32); + const encKey = await this.encryptService.encryptBytes(keyBytes, key); + await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId); + } + + return clientKeyHalf; + } } diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts index fb150f2a653..c55980068b5 100644 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts @@ -85,8 +85,9 @@ export default class OsBiometricsServiceLinux implements OsBiometricService { } async authenticateBiometric(): Promise { - const hwnd = Buffer.from(""); - return await biometrics.prompt(hwnd, ""); + //const hwnd = Buffer.from(""); + //return await biometrics.prompt(hwnd, ""); + return true; } async osSupportsBiometric(): Promise { @@ -105,7 +106,8 @@ export default class OsBiometricsServiceLinux implements OsBiometricService { } // check whether the polkit policy is loaded via dbus call to polkit - return !(await biometrics.available()); + //return !(await biometrics.available()); + return false; } async osBiometricsCanAutoSetup(): Promise { diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts index 1404d65ae51..c7ed88d390f 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -34,8 +34,14 @@ export class RendererBiometricsService extends DesktopBiometricsService { return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id); } - async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { - return await ipc.keyManagement.biometric.setBiometricProtectedUnlockKeyForUser(userId, value); + async setBiometricProtectedUnlockKeyForUser( + userId: UserId, + value: SymmetricCryptoKey, + ): Promise { + return await ipc.keyManagement.biometric.setBiometricProtectedUnlockKeyForUser( + userId, + value.toBase64(), + ); } async deleteBiometricUnlockKeyForUser(userId: UserId): Promise { @@ -46,10 +52,6 @@ export class RendererBiometricsService extends DesktopBiometricsService { return await ipc.keyManagement.biometric.setupBiometrics(); } - async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise { - return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value); - } - async getShouldAutopromptNow(): Promise { return await ipc.keyManagement.biometric.getShouldAutoprompt(); } diff --git a/apps/desktop/src/key-management/electron-key.service.spec.ts b/apps/desktop/src/key-management/electron-key.service.spec.ts index 7a0464f5e27..579288a100e 100644 --- a/apps/desktop/src/key-management/electron-key.service.spec.ts +++ b/apps/desktop/src/key-management/electron-key.service.spec.ts @@ -80,7 +80,6 @@ describe("ElectronKeyService", () => { await keyService.setUserKey(userKey, mockUserId); - expect(biometricService.setClientKeyHalfForUser).not.toHaveBeenCalled(); expect(biometricService.setBiometricProtectedUnlockKeyForUser).not.toHaveBeenCalled(); expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled(); expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId); @@ -96,7 +95,6 @@ describe("ElectronKeyService", () => { await keyService.setUserKey(userKey, mockUserId); - expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith(mockUserId, null); expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( mockUserId, userKey.keyB64, @@ -123,10 +121,6 @@ describe("ElectronKeyService", () => { await keyService.setUserKey(userKey, mockUserId); - expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith( - mockUserId, - clientKeyHalf, - ); expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( mockUserId, userKey.keyB64, @@ -158,10 +152,6 @@ describe("ElectronKeyService", () => { await keyService.setUserKey(userKey, mockUserId); - expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith( - mockUserId, - clientKeyHalf, - ); expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( mockUserId, userKey.keyB64, diff --git a/apps/desktop/src/key-management/electron-key.service.ts b/apps/desktop/src/key-management/electron-key.service.ts index 2941276720c..bc18152b0fb 100644 --- a/apps/desktop/src/key-management/electron-key.service.ts +++ b/apps/desktop/src/key-management/electron-key.service.ts @@ -8,9 +8,7 @@ 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 { @@ -77,10 +75,7 @@ export class ElectronKeyService extends DefaultKeyService { } private async storeBiometricsProtectedUserKey(userKey: UserKey, userId: UserId): Promise { - // 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); + await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey); } protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId: UserId): Promise { @@ -91,34 +86,4 @@ export class ElectronKeyService extends DefaultKeyService { await this.biometricService.deleteBiometricUnlockKeyForUser(userId); await super.clearAllStoredUserKeys(userId); } - - private async getBiometricEncryptionClientKeyHalf( - userKey: UserKey, - userId: UserId, - ): Promise { - 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; - } } diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index 3e90c27ab03..7f8576b8472 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -25,12 +25,13 @@ const biometric = { action: BiometricAction.GetStatusForUser, userId: userId, } satisfies BiometricMessage), - setBiometricProtectedUnlockKeyForUser: (userId: string, value: string): Promise => - ipcRenderer.invoke("biometric", { + setBiometricProtectedUnlockKeyForUser: (userId: string, keyB64: string): Promise => { + return ipcRenderer.invoke("biometric", { action: BiometricAction.SetKeyForUser, userId: userId, - key: value, - } satisfies BiometricMessage), + key: keyB64, + } satisfies BiometricMessage); + }, deleteBiometricUnlockKeyForUser: (userId: string): Promise => ipcRenderer.invoke("biometric", { action: BiometricAction.RemoveKeyForUser, @@ -40,12 +41,6 @@ const biometric = { ipcRenderer.invoke("biometric", { action: BiometricAction.Setup, } satisfies BiometricMessage), - setClientKeyHalf: (userId: string, value: string | null): Promise => - ipcRenderer.invoke("biometric", { - action: BiometricAction.SetClientKeyHalf, - userId: userId, - key: value, - } satisfies BiometricMessage), getShouldAutoprompt: (): Promise => ipcRenderer.invoke("biometric", { action: BiometricAction.GetShouldAutoprompt, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 20c632ec4ac..a8b567edb08 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -10,6 +10,7 @@ import { Subject, firstValueFrom } from "rxjs"; import { SsoUrlService } from "@bitwarden/auth/common"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { ClientType } from "@bitwarden/common/enums"; +import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { Message, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- For dependency creation @@ -182,7 +183,11 @@ export class Main { this.desktopSettingsService = new DesktopSettingsService(stateProvider); const biometricStateService = new DefaultBiometricStateService(stateProvider); - + const encryptService = new EncryptServiceImplementation( + this.mainCryptoFunctionService, + this.logService, + true, + ); this.biometricsService = new MainBiometricsService( this.i18nService, this.windowMain, @@ -190,6 +195,8 @@ export class Main { this.messagingService, process.platform, biometricStateService, + encryptService, + this.mainCryptoFunctionService, ); this.windowMain = new WindowMain( diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index 7616b265005..9711b49496d 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -9,8 +9,6 @@ export enum BiometricAction { SetKeyForUser = "setKeyForUser", RemoveKeyForUser = "removeKeyForUser", - SetClientKeyHalf = "setClientKeyHalf", - Setup = "setup", GetShouldAutoprompt = "getShouldAutoprompt", @@ -18,21 +16,13 @@ export enum BiometricAction { } export type BiometricMessage = - | { - action: BiometricAction.SetClientKeyHalf; - userId: string; - key: string | null; - } | { action: BiometricAction.SetKeyForUser; userId: string; key: string; } | { - action: Exclude< - BiometricAction, - BiometricAction.SetClientKeyHalf | BiometricAction.SetKeyForUser - >; + action: Exclude; userId?: string; data?: any; };