From 8e9502943977bc724429fbebaf2f27fa4f57aff5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 14 Jan 2025 16:20:18 +0100 Subject: [PATCH] [PM-16985] Fix biometrics not working in firefox or windows (#12833) * Fix biometrics not working in firefox or windows * Remove logs * Update badge after biometric unlock * Add removal todo note * Remove debug logging * Fix type warnings * Fix userkey typing in background biometrics service * Simplify types for userkey in foreground-browser-biometrics and runtime.background.ts --- .../settings/account-security.component.ts | 11 +++++- .../browser/src/background/main.background.ts | 12 ++++--- .../background-browser-biometrics.service.ts | 34 +++++++++++++++++-- .../foreground-browser-biometrics.ts | 5 ++- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 3b1727f89ac..1a64d860e45 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -507,7 +507,16 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const biometricsPromise = async () => { try { - const result = await this.biometricsService.authenticateWithBiometrics(); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a.id)), + ); + let result = false; + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); + result = await this.keyService.validateUserKey(userKey, userId); + } catch (e) { + result = false; + } // prevent duplicate dialog biometricsResponseReceived = true; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 4bec3d6cc0a..98f3867e5ff 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -632,11 +632,6 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); - this.biometricsService = new BackgroundBrowserBiometricsService( - runtimeNativeMessagingBackground, - this.logService, - ); - this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.pinService = new PinService( @@ -665,6 +660,13 @@ export default class MainBackground { this.kdfConfigService, ); + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + this.logService, + this.keyService, + this.biometricStateService, + ); + this.appIdService = new AppIdService(this.storageService, this.logService); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 8e6fc562d14..a0f54c58a64 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -1,9 +1,17 @@ import { Injectable } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.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"; -import { BiometricsService, BiometricsCommands, BiometricsStatus } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsCommands, + BiometricsStatus, + KeyService, + BiometricStateService, +} from "@bitwarden/key-management"; import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; import { BrowserApi } from "../../platform/browser/browser-api"; @@ -13,6 +21,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { constructor( private nativeMessagingBackground: () => NativeMessagingBackground, private logService: LogService, + private keyService: KeyService, + private biometricStateService: BiometricStateService, ) { super(); } @@ -74,12 +84,20 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); + // todo remove after 2025.3 if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { const response = await this.nativeMessagingBackground().callCommand({ command: BiometricsCommands.Unlock, }); if (response.response == "unlocked") { - return response.userKeyB64; + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.biometricStateService.setFingerprintValidated(true); + this.keyService.setUserKey(userKey, userId); + return userKey; + } } else { return null; } @@ -89,7 +107,15 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: userId, }); if (response.response) { - return response.userKeyB64; + // In case the requesting foreground context dies (popup), the userkey should still be set, so the user is unlocked / the setting should be enabled + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.biometricStateService.setFingerprintValidated(true); + this.keyService.setUserKey(userKey, userId); + return userKey; + } } else { return null; } @@ -98,6 +124,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { this.logService.info("Biometric unlock for user failed", e); throw new Error("Biometric unlock failed"); } + + return null; } async getBiometricsStatusForUser(id: UserId): Promise { diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index 0235ad5bd9c..43cc25e4dad 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -1,4 +1,3 @@ -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"; import { BiometricsCommands, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; @@ -29,13 +28,13 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { async unlockWithBiometricsForUser(userId: UserId): Promise { const response = await BrowserApi.sendMessageWithResponse<{ - result: string; + result: UserKey; error: string; }>(BiometricsCommands.UnlockWithBiometricsForUser, { userId }); if (!response.result) { return null; } - return SymmetricCryptoKey.fromString(response.result) as UserKey; + return response.result; } async getBiometricsStatusForUser(id: UserId): Promise {