mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
[PM-18680] biometric's no client key half provided for user (#13609)
* biometric's no client key half provided for user Biometric's client key half can be optional (null) when the password is not required on start of the application * improved unit test coverage * ipc setClientKeyHalf can be null
This commit is contained in:
@@ -1,187 +0,0 @@
|
|||||||
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 { 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("biometrics tests", 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -11,5 +11,5 @@ export abstract class DesktopBiometricsService extends BiometricsService {
|
|||||||
|
|
||||||
abstract setupBiometrics(): Promise<void>;
|
abstract setupBiometrics(): Promise<void>;
|
||||||
|
|
||||||
abstract setClientKeyHalfForUser(userId: UserId, value: string): Promise<void>;
|
abstract setClientKeyHalfForUser(userId: UserId, value: string | null): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ export class MainBiometricsIPCListener {
|
|||||||
message.userId as UserId,
|
message.userId as UserId,
|
||||||
);
|
);
|
||||||
case BiometricAction.SetClientKeyHalf:
|
case BiometricAction.SetClientKeyHalf:
|
||||||
if (message.key == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return await this.biometricService.setClientKeyHalfForUser(
|
return await this.biometricService.setClientKeyHalfForUser(
|
||||||
message.userId as UserId,
|
message.userId as UserId,
|
||||||
message.key,
|
message.key,
|
||||||
|
|||||||
@@ -0,0 +1,426 @@
|
|||||||
|
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!.encType).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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,7 +13,7 @@ import { OsBiometricService } from "./os-biometrics.service";
|
|||||||
|
|
||||||
export class MainBiometricsService extends DesktopBiometricsService {
|
export class MainBiometricsService extends DesktopBiometricsService {
|
||||||
private osBiometricsService: OsBiometricService;
|
private osBiometricsService: OsBiometricService;
|
||||||
private clientKeyHalves = new Map<string, string>();
|
private clientKeyHalves = new Map<string, string | null>();
|
||||||
private shouldAutoPrompt = true;
|
private shouldAutoPrompt = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -104,7 +104,7 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
return await this.osBiometricsService.osBiometricsSetup();
|
return await this.osBiometricsService.osBiometricsSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setClientKeyHalfForUser(userId: UserId, value: string): Promise<void> {
|
async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise<void> {
|
||||||
this.clientKeyHalves.set(userId, value);
|
this.clientKeyHalves.set(userId, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
const biometricKey = await this.osBiometricsService.getBiometricKey(
|
const biometricKey = await this.osBiometricsService.getBiometricKey(
|
||||||
"Bitwarden_biometric",
|
"Bitwarden_biometric",
|
||||||
`${userId}_user_biometric`,
|
`${userId}_user_biometric`,
|
||||||
this.clientKeyHalves.get(userId),
|
this.clientKeyHalves.get(userId) ?? undefined,
|
||||||
);
|
);
|
||||||
if (biometricKey == null) {
|
if (biometricKey == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -136,7 +136,7 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
service,
|
service,
|
||||||
storageKey,
|
storageKey,
|
||||||
value,
|
value,
|
||||||
this.clientKeyHalves.get(userId),
|
this.clientKeyHalves.get(userId) ?? undefined,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class RendererBiometricsService extends DesktopBiometricsService {
|
|||||||
return await ipc.keyManagement.biometric.setupBiometrics();
|
return await ipc.keyManagement.biometric.setupBiometrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setClientKeyHalfForUser(userId: UserId, value: string): Promise<void> {
|
async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise<void> {
|
||||||
return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value);
|
return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const biometric = {
|
|||||||
ipcRenderer.invoke("biometric", {
|
ipcRenderer.invoke("biometric", {
|
||||||
action: BiometricAction.Setup,
|
action: BiometricAction.Setup,
|
||||||
} satisfies BiometricMessage),
|
} satisfies BiometricMessage),
|
||||||
setClientKeyHalf: (userId: string, value: string): Promise<void> =>
|
setClientKeyHalf: (userId: string, value: string | null): Promise<void> =>
|
||||||
ipcRenderer.invoke("biometric", {
|
ipcRenderer.invoke("biometric", {
|
||||||
action: BiometricAction.SetClientKeyHalf,
|
action: BiometricAction.SetClientKeyHalf,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class ElectronKeyService extends DefaultKeyService {
|
|||||||
// May resolve to null, in which case no client key have is required
|
// May resolve to null, in which case no client key have is required
|
||||||
// TODO: Move to windows implementation
|
// TODO: Move to windows implementation
|
||||||
const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId);
|
const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId);
|
||||||
await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf as string);
|
await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf);
|
||||||
await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64);
|
await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,22 @@ export enum BiometricAction {
|
|||||||
SetShouldAutoprompt = "setShouldAutoprompt",
|
SetShouldAutoprompt = "setShouldAutoprompt",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BiometricMessage = {
|
export type BiometricMessage =
|
||||||
action: BiometricAction;
|
| {
|
||||||
key?: string;
|
action: BiometricAction.SetClientKeyHalf;
|
||||||
|
userId: string;
|
||||||
|
key: string | null;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
action: BiometricAction.SetKeyForUser;
|
||||||
|
userId: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
action: Exclude<
|
||||||
|
BiometricAction,
|
||||||
|
BiometricAction.SetClientKeyHalf | BiometricAction.SetKeyForUser
|
||||||
|
>;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user