1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-16 01:21:48 +01:00

[PM-16612] Prevent emergency access fingerprint confirmation dialog being spoofable (#12651)

* Prevent emergency access dialog spoofing

* Fix tests
This commit is contained in:
Bernd Schoolmann 2025-01-09 16:06:14 +01:00 committed by GitHub
parent 1b64bc2462
commit 11a7eb2f73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 25 additions and 25 deletions

View File

@ -117,14 +117,7 @@ describe("EmergencyAccessService", () => {
const granteeId = "grantee-id";
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
const mockPublicKeyB64 = "some-public-key-in-base64";
// const publicKey = Utils.fromB64ToArray(publicKeyB64);
const mockUserPublicKeyResponse = new UserKeyResponse({
UserId: granteeId,
PublicKey: mockPublicKeyB64,
});
const publicKey = new Uint8Array(64);
const mockUserPublicKeyEncryptedUserKey = new EncString(
EncryptionType.AesCbc256_HmacSha256_B64,
@ -132,14 +125,13 @@ describe("EmergencyAccessService", () => {
);
keyService.getUserKey.mockResolvedValueOnce(mockUserKey);
apiService.getUserPublicKey.mockResolvedValueOnce(mockUserPublicKeyResponse);
encryptService.rsaEncrypt.mockResolvedValueOnce(mockUserPublicKeyEncryptedUserKey);
emergencyAccessApiService.postEmergencyAccessConfirm.mockResolvedValueOnce();
// Act
await emergencyAccessService.confirm(id, granteeId);
await emergencyAccessService.confirm(id, granteeId, publicKey);
// Assert
expect(emergencyAccessApiService.postEmergencyAccessConfirm).toHaveBeenCalledWith(id, {

View File

@ -153,14 +153,13 @@ export class EmergencyAccessService
* Intended for grantor.
* @param id emergency access id
* @param token secret token provided in email
* @param publicKey public key of grantee
*/
async confirm(id: string, granteeId: string) {
async confirm(id: string, granteeId: string, publicKey: Uint8Array): Promise<void> {
const userKey = await this.keyService.getUserKey();
if (!userKey) {
throw new Error("No user key found");
}
const publicKeyResponse = await this.apiService.getUserPublicKey(granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
try {
this.logService.debug(

View File

@ -4,10 +4,8 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, OnInit, Inject } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
@ -21,6 +19,8 @@ type EmergencyAccessConfirmDialogData = {
userId: string;
/** traces a unique emergency request */
emergencyAccessId: string;
/** user public key */
publicKey: Uint8Array;
};
@Component({
selector: "emergency-access-confirm",
@ -36,7 +36,6 @@ export class EmergencyAccessConfirmComponent implements OnInit {
constructor(
@Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData,
private formBuilder: FormBuilder,
private apiService: ApiService,
private keyService: KeyService,
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
private logService: LogService,
@ -45,13 +44,12 @@ export class EmergencyAccessConfirmComponent implements OnInit {
async ngOnInit() {
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId);
if (publicKeyResponse != null) {
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const fingerprint = await this.keyService.getFingerprint(this.params.userId, publicKey);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}
const fingerprint = await this.keyService.getFingerprint(
this.params.userId,
this.params.publicKey,
);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}
} catch (e) {
this.logService.error(e);

View File

@ -4,6 +4,7 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@ -13,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService, ToastService } from "@bitwarden/components";
import { EmergencyAccessService } from "../../emergency-access";
@ -70,6 +72,7 @@ export class EmergencyAccessComponent implements OnInit {
billingAccountProfileStateService: BillingAccountProfileStateService,
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
private toastService: ToastService,
private apiService: ApiService,
private accountService: AccountService,
) {
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
@ -147,6 +150,9 @@ export class EmergencyAccessComponent implements OnInit {
return;
}
const publicKeyResponse = await this.apiService.getUserPublicKey(contact.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const autoConfirm = await firstValueFrom(
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
);
@ -156,11 +162,12 @@ export class EmergencyAccessComponent implements OnInit {
name: this.userNamePipe.transform(contact),
emergencyAccessId: contact.id,
userId: contact?.granteeId,
publicKey,
},
});
const result = await lastValueFrom(dialogRef.closed);
if (result === EmergencyAccessConfirmDialogResult.Confirmed) {
await this.emergencyAccessService.confirm(contact.id, contact.granteeId);
await this.emergencyAccessService.confirm(contact.id, contact.granteeId, publicKey);
updateUser();
this.toastService.showToast({
variant: "success",
@ -171,7 +178,11 @@ export class EmergencyAccessComponent implements OnInit {
return;
}
this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
this.actionPromise = this.emergencyAccessService.confirm(
contact.id,
contact.granteeId,
publicKey,
);
await this.actionPromise;
updateUser();