1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-29 12:55:21 +01:00

[PM-2192] Improve userkey verification on biometric unlock (#10326)

* Improve biometric unlock userkey verification

* Add early return

* Pass activeuserid to cryptoservice functions
This commit is contained in:
Bernd Schoolmann 2024-08-06 21:35:04 +02:00 committed by GitHub
parent b84becd9e4
commit 7cd6fcf265
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 48 additions and 18 deletions

View File

@ -2000,6 +2000,12 @@
"nativeMessagingWrongUserTitle": { "nativeMessagingWrongUserTitle": {
"message": "Account missmatch" "message": "Account missmatch"
}, },
"nativeMessagingWrongUserKeyDesc": {
"message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again."
},
"nativeMessagingWrongUserKeyTitle": {
"message": "Biometric key missmatch"
},
"biometricsNotEnabledTitle": { "biometricsNotEnabledTitle": {
"message": "Biometrics not set up" "message": "Biometrics not set up"
}, },

View File

@ -1049,6 +1049,7 @@ export default class MainBackground {
this.logService, this.logService,
this.authService, this.authService,
this.biometricStateService, this.biometricStateService,
this.accountService,
); );
this.commandsBackground = new CommandsBackground( this.commandsBackground = new CommandsBackground(
this, this,

View File

@ -1,5 +1,6 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -81,6 +82,7 @@ export class NativeMessagingBackground {
private logService: LogService, private logService: LogService,
private authService: AuthService, private authService: AuthService,
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
private accountService: AccountService,
) { ) {
if (chrome?.permissions?.onAdded) { if (chrome?.permissions?.onAdded) {
// Reload extension to activate nativeMessaging // Reload extension to activate nativeMessaging
@ -223,6 +225,16 @@ export class NativeMessagingBackground {
}); });
} }
showIncorrectUserKeyDialog() {
this.messagingService.send("showDialog", {
title: { key: "nativeMessagingWrongUserKeyTitle" },
content: { key: "nativeMessagingWrongUserKeyDesc" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: "danger",
});
}
async send(message: Message) { async send(message: Message) {
if (!this.connected) { if (!this.connected) {
await this.connect(); await this.connect();
@ -350,7 +362,26 @@ export class NativeMessagingBackground {
const userKey = new SymmetricCryptoKey( const userKey = new SymmetricCryptoKey(
Utils.fromB64ToArray(message.userKeyB64), Utils.fromB64ToArray(message.userKeyB64),
) as UserKey; ) as UserKey;
await this.cryptoService.setUserKey(userKey); const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const isUserKeyValid = await this.cryptoService.validateUserKey(
userKey,
activeUserId,
);
if (isUserKeyValid) {
await this.cryptoService.setUserKey(userKey, activeUserId);
} else {
this.logService.error("Unable to verify biometric unlocked userkey");
await this.cryptoService.clearKeys(activeUserId);
this.showIncorrectUserKeyDialog();
// Exit early
if (this.resolver) {
this.resolver(message);
}
return;
}
} else { } else {
throw new Error("No key received"); throw new Error("No key received");
} }
@ -371,21 +402,6 @@ export class NativeMessagingBackground {
return; return;
} }
// Verify key is correct by attempting to decrypt a secret
try {
await this.cryptoService.getFingerprint(await this.stateService.getUserId());
} catch (e) {
this.logService.error("Unable to verify key: " + e);
await this.cryptoService.clearKeys();
this.showWrongUserDialog();
// Exit early
if (this.resolver) {
this.resolver(message);
}
return;
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.runtimeBackground.processMessage({ command: "unlocked" }); this.runtimeBackground.processMessage({ command: "unlocked" });

View File

@ -418,4 +418,11 @@ export abstract class CryptoService {
* @throws If an invalid user id is passed in. * @throws If an invalid user id is passed in.
*/ */
abstract userPublicKey$(userId: UserId): Observable<UserPublicKey>; abstract userPublicKey$(userId: UserId): Observable<UserPublicKey>;
/**
* Validates that a userkey is correct for a given user
* @param key The key to validate
* @param userId The user id for the key
*/
abstract validateUserKey(key: UserKey, userId: UserId): Promise<boolean>;
} }

View File

@ -620,7 +620,7 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
// ---HELPERS--- // ---HELPERS---
protected async validateUserKey(key: UserKey, userId: UserId): Promise<boolean> { async validateUserKey(key: UserKey, userId: UserId): Promise<boolean> {
if (!key) { if (!key) {
return false; return false;
} }