1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00: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
4 changed files with 25 additions and 25 deletions

View File

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

View File

@@ -153,14 +153,13 @@ export class EmergencyAccessService
* Intended for grantor. * Intended for grantor.
* @param id emergency access id * @param id emergency access id
* @param token secret token provided in email * @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(); const userKey = await this.keyService.getUserKey();
if (!userKey) { if (!userKey) {
throw new Error("No user key found"); throw new Error("No user key found");
} }
const publicKeyResponse = await this.apiService.getUserPublicKey(granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
try { try {
this.logService.debug( 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 { Component, OnInit, Inject } from "@angular/core";
import { FormBuilder } from "@angular/forms"; 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 { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
@@ -21,6 +19,8 @@ type EmergencyAccessConfirmDialogData = {
userId: string; userId: string;
/** traces a unique emergency request */ /** traces a unique emergency request */
emergencyAccessId: string; emergencyAccessId: string;
/** user public key */
publicKey: Uint8Array;
}; };
@Component({ @Component({
selector: "emergency-access-confirm", selector: "emergency-access-confirm",
@@ -36,7 +36,6 @@ export class EmergencyAccessConfirmComponent implements OnInit {
constructor( constructor(
@Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData, @Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private apiService: ApiService,
private keyService: KeyService, private keyService: KeyService,
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
private logService: LogService, private logService: LogService,
@@ -45,13 +44,12 @@ export class EmergencyAccessConfirmComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
try { try {
const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId); const fingerprint = await this.keyService.getFingerprint(
if (publicKeyResponse != null) { this.params.userId,
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); this.params.publicKey,
const fingerprint = await this.keyService.getFingerprint(this.params.userId, publicKey); );
if (fingerprint != null) { if (fingerprint != null) {
this.fingerprint = fingerprint.join("-"); this.fingerprint = fingerprint.join("-");
}
} }
} catch (e) { } catch (e) {
this.logService.error(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 { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; 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 { 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 { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.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 { DialogService, ToastService } from "@bitwarden/components";
import { EmergencyAccessService } from "../../emergency-access"; import { EmergencyAccessService } from "../../emergency-access";
@@ -70,6 +72,7 @@ export class EmergencyAccessComponent implements OnInit {
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
private toastService: ToastService, private toastService: ToastService,
private apiService: ApiService,
private accountService: AccountService, private accountService: AccountService,
) { ) {
this.canAccessPremium$ = this.accountService.activeAccount$.pipe( this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
@@ -147,6 +150,9 @@ export class EmergencyAccessComponent implements OnInit {
return; return;
} }
const publicKeyResponse = await this.apiService.getUserPublicKey(contact.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const autoConfirm = await firstValueFrom( const autoConfirm = await firstValueFrom(
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$, this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
); );
@@ -156,11 +162,12 @@ export class EmergencyAccessComponent implements OnInit {
name: this.userNamePipe.transform(contact), name: this.userNamePipe.transform(contact),
emergencyAccessId: contact.id, emergencyAccessId: contact.id,
userId: contact?.granteeId, userId: contact?.granteeId,
publicKey,
}, },
}); });
const result = await lastValueFrom(dialogRef.closed); const result = await lastValueFrom(dialogRef.closed);
if (result === EmergencyAccessConfirmDialogResult.Confirmed) { if (result === EmergencyAccessConfirmDialogResult.Confirmed) {
await this.emergencyAccessService.confirm(contact.id, contact.granteeId); await this.emergencyAccessService.confirm(contact.id, contact.granteeId, publicKey);
updateUser(); updateUser();
this.toastService.showToast({ this.toastService.showToast({
variant: "success", variant: "success",
@@ -171,7 +178,11 @@ export class EmergencyAccessComponent implements OnInit {
return; return;
} }
this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId); this.actionPromise = this.emergencyAccessService.confirm(
contact.id,
contact.granteeId,
publicKey,
);
await this.actionPromise; await this.actionPromise;
updateUser(); updateUser();