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:
@@ -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, {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user