1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts
Bernd Schoolmann 43b1f55360 [PM-18697] Remove old symmetric key representations in symmetriccryptokey (#13598)
* Remove AES128CBC-HMAC encryption

* Increase test coverage

* Refactor symmetric keys and increase test coverage

* Re-add type 0 encryption

* Fix ts strict warning

* Remove old symmetric key representations in symmetriccryptokey

* Fix desktop build

* Fix test

* Fix build

* Update libs/common/src/key-management/crypto/services/web-crypto-function.service.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/node/src/services/node-crypto-function.service.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Undo changes

* Remove cast

* Undo changes to tests

* Fix linting

* Undo removing new Uint8Array in aesDecryptFastParameters

* Fix merge conflicts

* Fix test

* Fix another test

* Fix test

---------

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
2025-04-21 14:57:26 +00:00

427 lines
14 KiB
TypeScript

import { mock, MockProxy } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import {
BiometricsService,
BiometricsStatus,
BiometricStateService,
} from "@bitwarden/key-management";
import { WindowMain } from "../../main/window.main";
import { MainBiometricsService } from "./main-biometrics.service";
import OsBiometricsServiceLinux from "./os-biometrics-linux.service";
import OsBiometricsServiceMac from "./os-biometrics-mac.service";
import OsBiometricsServiceWindows from "./os-biometrics-windows.service";
import { OsBiometricService } from "./os-biometrics.service";
jest.mock("@bitwarden/desktop-napi", () => {
return {
biometrics: jest.fn(),
passwords: jest.fn(),
};
});
describe("MainBiometricsService", function () {
const i18nService = mock<I18nService>();
const windowMain = mock<WindowMain>();
const logService = mock<LogService>();
const messagingService = mock<MessagingService>();
const biometricStateService = mock<BiometricStateService>();
it("Should call the platformspecific methods", async () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
const mockService = mock<OsBiometricService>();
(sut as any).osBiometricsService = mockService;
await sut.authenticateBiometric();
expect(mockService.authenticateBiometric).toBeCalled();
});
describe("Should create a platform specific service", function () {
it("Should create a biometrics service specific for Windows", () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
"win32",
biometricStateService,
);
const internalService = (sut as any).osBiometricsService;
expect(internalService).not.toBeNull();
expect(internalService).toBeInstanceOf(OsBiometricsServiceWindows);
});
it("Should create a biometrics service specific for MacOs", () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
"darwin",
biometricStateService,
);
const internalService = (sut as any).osBiometricsService;
expect(internalService).not.toBeNull();
expect(internalService).toBeInstanceOf(OsBiometricsServiceMac);
});
it("Should create a biometrics service specific for Linux", () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
"linux",
biometricStateService,
);
const internalService = (sut as any).osBiometricsService;
expect(internalService).not.toBeNull();
expect(internalService).toBeInstanceOf(OsBiometricsServiceLinux);
});
});
describe("can auth biometric", () => {
let sut: BiometricsService;
let innerService: MockProxy<OsBiometricService>;
beforeEach(() => {
sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
innerService = mock();
(sut as any).osBiometricsService = innerService;
});
it("should return the correct biometric status for system status", async () => {
const testCases = [
// happy path
[true, false, false, BiometricsStatus.Available],
[false, true, true, BiometricsStatus.HardwareUnavailable],
[true, true, true, BiometricsStatus.AutoSetupNeeded],
[true, true, false, BiometricsStatus.ManualSetupNeeded],
// should not happen
[false, false, true, BiometricsStatus.HardwareUnavailable],
[true, false, true, BiometricsStatus.Available],
[false, true, false, BiometricsStatus.HardwareUnavailable],
[false, false, false, BiometricsStatus.HardwareUnavailable],
];
for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) {
innerService.osSupportsBiometric.mockResolvedValue(supportsBiometric as boolean);
innerService.osBiometricsNeedsSetup.mockResolvedValue(needsSetup as boolean);
innerService.osBiometricsCanAutoSetup.mockResolvedValue(canAutoSetup as boolean);
const actual = await sut.getBiometricsStatus();
expect(actual).toBe(expected);
}
});
it("should return the correct biometric status for user status", async () => {
const testCases = [
// system status, biometric unlock enabled, require password on start, has key half, result
[BiometricsStatus.Available, false, false, false, BiometricsStatus.NotEnabledLocally],
[BiometricsStatus.Available, false, true, false, BiometricsStatus.NotEnabledLocally],
[BiometricsStatus.Available, false, false, true, BiometricsStatus.NotEnabledLocally],
[BiometricsStatus.Available, false, true, true, BiometricsStatus.NotEnabledLocally],
[
BiometricsStatus.PlatformUnsupported,
true,
true,
true,
BiometricsStatus.PlatformUnsupported,
],
[BiometricsStatus.ManualSetupNeeded, true, true, true, BiometricsStatus.ManualSetupNeeded],
[BiometricsStatus.AutoSetupNeeded, true, true, true, BiometricsStatus.AutoSetupNeeded],
[BiometricsStatus.Available, true, false, true, BiometricsStatus.Available],
[BiometricsStatus.Available, true, true, false, BiometricsStatus.UnlockNeeded],
[BiometricsStatus.Available, true, false, true, BiometricsStatus.Available],
];
for (const [
systemStatus,
unlockEnabled,
requirePasswordOnStart,
hasKeyHalf,
expected,
] of testCases) {
sut.getBiometricsStatus = jest.fn().mockResolvedValue(systemStatus as BiometricsStatus);
biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(unlockEnabled as boolean);
biometricStateService.getRequirePasswordOnStart.mockResolvedValue(
requirePasswordOnStart as boolean,
);
(sut as any).clientKeyHalves = new Map();
const userId = "test" as UserId;
if (hasKeyHalf) {
(sut as any).clientKeyHalves.set(userId, "test");
}
const actual = await sut.getBiometricsStatusForUser(userId);
expect(actual).toBe(expected);
}
});
});
describe("setupBiometrics", () => {
it("should call the platform specific setup method", async () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
const osBiometricsService = mock<OsBiometricService>();
(sut as any).osBiometricsService = osBiometricsService;
await sut.setupBiometrics();
expect(osBiometricsService.osBiometricsSetup).toHaveBeenCalled();
});
});
describe("setClientKeyHalfForUser", () => {
let sut: MainBiometricsService;
beforeEach(() => {
sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
});
it("should set the client key half for the user", async () => {
const userId = "test" as UserId;
const keyHalf = "testKeyHalf";
await sut.setClientKeyHalfForUser(userId, keyHalf);
expect((sut as any).clientKeyHalves.has(userId)).toBe(true);
expect((sut as any).clientKeyHalves.get(userId)).toBe(keyHalf);
});
it("should reset the client key half for the user", async () => {
const userId = "test" as UserId;
await sut.setClientKeyHalfForUser(userId, null);
expect((sut as any).clientKeyHalves.has(userId)).toBe(true);
expect((sut as any).clientKeyHalves.get(userId)).toBe(null);
});
});
describe("authenticateWithBiometrics", () => {
it("should call the platform specific authenticate method", async () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
const osBiometricsService = mock<OsBiometricService>();
(sut as any).osBiometricsService = osBiometricsService;
await sut.authenticateWithBiometrics();
expect(osBiometricsService.authenticateBiometric).toHaveBeenCalled();
});
});
describe("unlockWithBiometricsForUser", () => {
let sut: MainBiometricsService;
let osBiometricsService: MockProxy<OsBiometricService>;
beforeEach(() => {
sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
osBiometricsService = mock<OsBiometricService>();
(sut as any).osBiometricsService = osBiometricsService;
});
it("should return null if no biometric key is returned ", async () => {
const userId = "test" as UserId;
(sut as any).clientKeyHalves.set(userId, "testKeyHalf");
const userKey = await sut.unlockWithBiometricsForUser(userId);
expect(userKey).toBeNull();
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(
"Bitwarden_biometric",
`${userId}_user_biometric`,
"testKeyHalf",
);
});
it("should return the biometric key if a valid key is returned", async () => {
const userId = "test" as UserId;
(sut as any).clientKeyHalves.set(userId, "testKeyHalf");
const biometricKey = Utils.fromBufferToB64(new Uint8Array(64));
osBiometricsService.getBiometricKey.mockResolvedValue(biometricKey);
const userKey = await sut.unlockWithBiometricsForUser(userId);
expect(userKey).not.toBeNull();
expect(userKey!.keyB64).toBe(biometricKey);
expect(userKey!.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(
"Bitwarden_biometric",
`${userId}_user_biometric`,
"testKeyHalf",
);
});
});
describe("setBiometricProtectedUnlockKeyForUser", () => {
let sut: MainBiometricsService;
let osBiometricsService: MockProxy<OsBiometricService>;
beforeEach(() => {
sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
osBiometricsService = mock<OsBiometricService>();
(sut as any).osBiometricsService = osBiometricsService;
});
it("should throw an error if no client key half is provided", async () => {
const userId = "test" as UserId;
const unlockKey = "testUnlockKey";
await expect(sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey)).rejects.toThrow(
"No client key half provided for user",
);
});
it("should call the platform specific setBiometricKey method", async () => {
const userId = "test" as UserId;
const unlockKey = "testUnlockKey";
(sut as any).clientKeyHalves.set(userId, "testKeyHalf");
await sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey);
expect(osBiometricsService.setBiometricKey).toHaveBeenCalledWith(
"Bitwarden_biometric",
`${userId}_user_biometric`,
unlockKey,
"testKeyHalf",
);
});
});
describe("deleteBiometricUnlockKeyForUser", () => {
it("should call the platform specific deleteBiometricKey method", async () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
const osBiometricsService = mock<OsBiometricService>();
(sut as any).osBiometricsService = osBiometricsService;
const userId = "test" as UserId;
await sut.deleteBiometricUnlockKeyForUser(userId);
expect(osBiometricsService.deleteBiometricKey).toHaveBeenCalledWith(
"Bitwarden_biometric",
`${userId}_user_biometric`,
);
});
});
describe("setShouldAutopromptNow", () => {
let sut: MainBiometricsService;
beforeEach(() => {
sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
});
it("should set shouldAutopromptNow to false", async () => {
await sut.setShouldAutopromptNow(false);
const shouldAutoPrompt = await sut.getShouldAutopromptNow();
expect(shouldAutoPrompt).toBe(false);
});
it("should set shouldAutopromptNow to true", async () => {
await sut.setShouldAutopromptNow(true);
const shouldAutoPrompt = await sut.getShouldAutopromptNow();
expect(shouldAutoPrompt).toBe(true);
});
});
describe("getShouldAutopromptNow", () => {
it("defaults shouldAutoPrompt is true", async () => {
const sut = new MainBiometricsService(
i18nService,
windowMain,
logService,
messagingService,
process.platform,
biometricStateService,
);
const shouldAutoPrompt = await sut.getShouldAutopromptNow();
expect(shouldAutoPrompt).toBe(true);
});
});
});