mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
[PM-22745] Move clientkeyhalf to os impl (#15140)
* Move clientkeyhalf to main * Move clientkeyhalf to os platform implementation * Cleanup * Fix tests * Tests * Add tests * Add tests * Fix types * Undo linux debugging changes * Fix typo * Update apps/desktop/src/key-management/biometrics/os-biometrics.service.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update apps/desktop/src/key-management/biometrics/os-biometrics.service.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update apps/desktop/src/key-management/biometrics/os-biometrics.service.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Fix build --------- Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { BiometricsService } from "@bitwarden/key-management";
|
import { BiometricsService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
@@ -6,10 +7,10 @@ import { BiometricsService } from "@bitwarden/key-management";
|
|||||||
* specifically for the main process.
|
* specifically for the main process.
|
||||||
*/
|
*/
|
||||||
export abstract class DesktopBiometricsService extends BiometricsService {
|
export abstract class DesktopBiometricsService extends BiometricsService {
|
||||||
abstract setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise<void>;
|
abstract setBiometricProtectedUnlockKeyForUser(
|
||||||
|
userId: UserId,
|
||||||
|
value: SymmetricCryptoKey,
|
||||||
|
): Promise<void>;
|
||||||
abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void>;
|
abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void>;
|
||||||
|
|
||||||
abstract setupBiometrics(): Promise<void>;
|
abstract setupBiometrics(): Promise<void>;
|
||||||
|
|
||||||
abstract setClientKeyHalfForUser(userId: UserId, value: string | null): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
@@ -37,17 +38,12 @@ export class MainBiometricsIPCListener {
|
|||||||
}
|
}
|
||||||
return await this.biometricService.setBiometricProtectedUnlockKeyForUser(
|
return await this.biometricService.setBiometricProtectedUnlockKeyForUser(
|
||||||
message.userId as UserId,
|
message.userId as UserId,
|
||||||
message.key,
|
SymmetricCryptoKey.fromString(message.key),
|
||||||
);
|
);
|
||||||
case BiometricAction.RemoveKeyForUser:
|
case BiometricAction.RemoveKeyForUser:
|
||||||
return await this.biometricService.deleteBiometricUnlockKeyForUser(
|
return await this.biometricService.deleteBiometricUnlockKeyForUser(
|
||||||
message.userId as UserId,
|
message.userId as UserId,
|
||||||
);
|
);
|
||||||
case BiometricAction.SetClientKeyHalf:
|
|
||||||
return await this.biometricService.setClientKeyHalfForUser(
|
|
||||||
message.userId as UserId,
|
|
||||||
message.key,
|
|
||||||
);
|
|
||||||
case BiometricAction.Setup:
|
case BiometricAction.Setup:
|
||||||
return await this.biometricService.setupBiometrics();
|
return await this.biometricService.setupBiometrics();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import {
|
import {
|
||||||
BiometricsService,
|
BiometricsService,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from "@bitwarden/key-management";
|
} from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { WindowMain } from "../../main/window.main";
|
import { WindowMain } from "../../main/window.main";
|
||||||
|
import { MainCryptoFunctionService } from "../../platform/main/main-crypto-function.service";
|
||||||
|
|
||||||
import { MainBiometricsService } from "./main-biometrics.service";
|
import { MainBiometricsService } from "./main-biometrics.service";
|
||||||
import OsBiometricsServiceLinux from "./os-biometrics-linux.service";
|
import OsBiometricsServiceLinux from "./os-biometrics-linux.service";
|
||||||
@@ -27,21 +28,25 @@ jest.mock("@bitwarden/desktop-napi", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const unlockKey = new SymmetricCryptoKey(new Uint8Array(64));
|
||||||
|
|
||||||
describe("MainBiometricsService", function () {
|
describe("MainBiometricsService", function () {
|
||||||
const i18nService = mock<I18nService>();
|
const i18nService = mock<I18nService>();
|
||||||
const windowMain = mock<WindowMain>();
|
const windowMain = mock<WindowMain>();
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
const messagingService = mock<MessagingService>();
|
|
||||||
const biometricStateService = mock<BiometricStateService>();
|
const biometricStateService = mock<BiometricStateService>();
|
||||||
|
const cryptoFunctionService = mock<MainCryptoFunctionService>();
|
||||||
|
const encryptService = mock<EncryptService>();
|
||||||
|
|
||||||
it("Should call the platformspecific methods", async () => {
|
it("Should call the platformspecific methods", async () => {
|
||||||
const sut = new MainBiometricsService(
|
const sut = new MainBiometricsService(
|
||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mockService = mock<OsBiometricService>();
|
const mockService = mock<OsBiometricService>();
|
||||||
@@ -57,9 +62,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
"win32",
|
"win32",
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const internalService = (sut as any).osBiometricsService;
|
const internalService = (sut as any).osBiometricsService;
|
||||||
@@ -72,9 +78,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
"darwin",
|
"darwin",
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
const internalService = (sut as any).osBiometricsService;
|
const internalService = (sut as any).osBiometricsService;
|
||||||
expect(internalService).not.toBeNull();
|
expect(internalService).not.toBeNull();
|
||||||
@@ -86,9 +93,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
"linux",
|
"linux",
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const internalService = (sut as any).osBiometricsService;
|
const internalService = (sut as any).osBiometricsService;
|
||||||
@@ -106,9 +114,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
innerService = mock();
|
innerService = mock();
|
||||||
@@ -131,9 +140,9 @@ describe("MainBiometricsService", function () {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) {
|
for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) {
|
||||||
innerService.osSupportsBiometric.mockResolvedValue(supportsBiometric as boolean);
|
innerService.supportsBiometrics.mockResolvedValue(supportsBiometric as boolean);
|
||||||
innerService.osBiometricsNeedsSetup.mockResolvedValue(needsSetup as boolean);
|
innerService.needsSetup.mockResolvedValue(needsSetup as boolean);
|
||||||
innerService.osBiometricsCanAutoSetup.mockResolvedValue(canAutoSetup as boolean);
|
innerService.canAutoSetup.mockResolvedValue(canAutoSetup as boolean);
|
||||||
|
|
||||||
const actual = await sut.getBiometricsStatus();
|
const actual = await sut.getBiometricsStatus();
|
||||||
expect(actual).toBe(expected);
|
expect(actual).toBe(expected);
|
||||||
@@ -175,12 +184,23 @@ describe("MainBiometricsService", function () {
|
|||||||
biometricStateService.getRequirePasswordOnStart.mockResolvedValue(
|
biometricStateService.getRequirePasswordOnStart.mockResolvedValue(
|
||||||
requirePasswordOnStart as boolean,
|
requirePasswordOnStart as boolean,
|
||||||
);
|
);
|
||||||
(sut as any).clientKeyHalves = new Map();
|
if (!requirePasswordOnStart) {
|
||||||
const userId = "test" as UserId;
|
(sut as any).osBiometricsService.getBiometricsFirstUnlockStatusForUser = jest
|
||||||
if (hasKeyHalf) {
|
.fn()
|
||||||
(sut as any).clientKeyHalves.set(userId, "test");
|
.mockResolvedValue(BiometricsStatus.Available);
|
||||||
|
} else {
|
||||||
|
if (hasKeyHalf) {
|
||||||
|
(sut as any).osBiometricsService.getBiometricsFirstUnlockStatusForUser = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(BiometricsStatus.Available);
|
||||||
|
} else {
|
||||||
|
(sut as any).osBiometricsService.getBiometricsFirstUnlockStatusForUser = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(BiometricsStatus.UnlockNeeded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = "test" as UserId;
|
||||||
const actual = await sut.getBiometricsStatusForUser(userId);
|
const actual = await sut.getBiometricsStatusForUser(userId);
|
||||||
expect(actual).toBe(expected);
|
expect(actual).toBe(expected);
|
||||||
}
|
}
|
||||||
@@ -193,50 +213,17 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
const osBiometricsService = mock<OsBiometricService>();
|
const osBiometricsService = mock<OsBiometricService>();
|
||||||
(sut as any).osBiometricsService = osBiometricsService;
|
(sut as any).osBiometricsService = osBiometricsService;
|
||||||
|
|
||||||
await sut.setupBiometrics();
|
await sut.setupBiometrics();
|
||||||
|
|
||||||
expect(osBiometricsService.osBiometricsSetup).toHaveBeenCalled();
|
expect(osBiometricsService.runSetup).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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -246,9 +233,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
const osBiometricsService = mock<OsBiometricService>();
|
const osBiometricsService = mock<OsBiometricService>();
|
||||||
(sut as any).osBiometricsService = osBiometricsService;
|
(sut as any).osBiometricsService = osBiometricsService;
|
||||||
@@ -268,9 +256,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
osBiometricsService = mock<OsBiometricService>();
|
osBiometricsService = mock<OsBiometricService>();
|
||||||
(sut as any).osBiometricsService = osBiometricsService;
|
(sut as any).osBiometricsService = osBiometricsService;
|
||||||
@@ -278,34 +267,24 @@ describe("MainBiometricsService", function () {
|
|||||||
|
|
||||||
it("should return null if no biometric key is returned ", async () => {
|
it("should return null if no biometric key is returned ", async () => {
|
||||||
const userId = "test" as UserId;
|
const userId = "test" as UserId;
|
||||||
(sut as any).clientKeyHalves.set(userId, "testKeyHalf");
|
osBiometricsService.getBiometricKey.mockResolvedValue(null);
|
||||||
|
|
||||||
const userKey = await sut.unlockWithBiometricsForUser(userId);
|
const userKey = await sut.unlockWithBiometricsForUser(userId);
|
||||||
|
|
||||||
expect(userKey).toBeNull();
|
expect(userKey).toBeNull();
|
||||||
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(
|
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(userId);
|
||||||
"Bitwarden_biometric",
|
|
||||||
`${userId}_user_biometric`,
|
|
||||||
"testKeyHalf",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return the biometric key if a valid key is returned", async () => {
|
it("should return the biometric key if a valid key is returned", async () => {
|
||||||
const userId = "test" as UserId;
|
const userId = "test" as UserId;
|
||||||
(sut as any).clientKeyHalves.set(userId, "testKeyHalf");
|
const biometricKey = new SymmetricCryptoKey(new Uint8Array(64));
|
||||||
const biometricKey = Utils.fromBufferToB64(new Uint8Array(64));
|
|
||||||
osBiometricsService.getBiometricKey.mockResolvedValue(biometricKey);
|
osBiometricsService.getBiometricKey.mockResolvedValue(biometricKey);
|
||||||
|
|
||||||
const userKey = await sut.unlockWithBiometricsForUser(userId);
|
const userKey = await sut.unlockWithBiometricsForUser(userId);
|
||||||
|
|
||||||
expect(userKey).not.toBeNull();
|
expect(userKey).not.toBeNull();
|
||||||
expect(userKey!.keyB64).toBe(biometricKey);
|
expect(userKey!.keyB64).toBe(biometricKey.toBase64());
|
||||||
expect(userKey!.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
|
expect(userKey!.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
|
||||||
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(
|
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(userId);
|
||||||
"Bitwarden_biometric",
|
|
||||||
`${userId}_user_biometric`,
|
|
||||||
"testKeyHalf",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -318,37 +297,21 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
osBiometricsService = mock<OsBiometricService>();
|
osBiometricsService = mock<OsBiometricService>();
|
||||||
(sut as any).osBiometricsService = osBiometricsService;
|
(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 () => {
|
it("should call the platform specific setBiometricKey method", async () => {
|
||||||
const userId = "test" as UserId;
|
const userId = "test" as UserId;
|
||||||
const unlockKey = "testUnlockKey";
|
|
||||||
|
|
||||||
(sut as any).clientKeyHalves.set(userId, "testKeyHalf");
|
|
||||||
|
|
||||||
await sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey);
|
await sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey);
|
||||||
|
|
||||||
expect(osBiometricsService.setBiometricKey).toHaveBeenCalledWith(
|
expect(osBiometricsService.setBiometricKey).toHaveBeenCalledWith(userId, unlockKey);
|
||||||
"Bitwarden_biometric",
|
|
||||||
`${userId}_user_biometric`,
|
|
||||||
unlockKey,
|
|
||||||
"testKeyHalf",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -358,9 +321,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
const osBiometricsService = mock<OsBiometricService>();
|
const osBiometricsService = mock<OsBiometricService>();
|
||||||
(sut as any).osBiometricsService = osBiometricsService;
|
(sut as any).osBiometricsService = osBiometricsService;
|
||||||
@@ -369,10 +333,7 @@ describe("MainBiometricsService", function () {
|
|||||||
|
|
||||||
await sut.deleteBiometricUnlockKeyForUser(userId);
|
await sut.deleteBiometricUnlockKeyForUser(userId);
|
||||||
|
|
||||||
expect(osBiometricsService.deleteBiometricKey).toHaveBeenCalledWith(
|
expect(osBiometricsService.deleteBiometricKey).toHaveBeenCalledWith(userId);
|
||||||
"Bitwarden_biometric",
|
|
||||||
`${userId}_user_biometric`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -384,9 +345,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -413,9 +375,10 @@ describe("MainBiometricsService", function () {
|
|||||||
i18nService,
|
i18nService,
|
||||||
windowMain,
|
windowMain,
|
||||||
logService,
|
logService,
|
||||||
messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
cryptoFunctionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldAutoPrompt = await sut.getShouldAutopromptNow();
|
const shouldAutoPrompt = await sut.getShouldAutopromptNow();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { UserKey } from "@bitwarden/common/types/key";
|
import { UserKey } from "@bitwarden/common/types/key";
|
||||||
@@ -13,16 +14,16 @@ 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 | null>();
|
|
||||||
private shouldAutoPrompt = true;
|
private shouldAutoPrompt = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private windowMain: WindowMain,
|
private windowMain: WindowMain,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private messagingService: MessagingService,
|
platform: NodeJS.Platform,
|
||||||
private platform: NodeJS.Platform,
|
|
||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
|
private encryptService: EncryptService,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (platform === "win32") {
|
if (platform === "win32") {
|
||||||
@@ -32,6 +33,9 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.windowMain,
|
this.windowMain,
|
||||||
this.logService,
|
this.logService,
|
||||||
|
this.biometricStateService,
|
||||||
|
this.encryptService,
|
||||||
|
this.cryptoFunctionService,
|
||||||
);
|
);
|
||||||
} else if (platform === "darwin") {
|
} else if (platform === "darwin") {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -40,7 +44,11 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
} else if (platform === "linux") {
|
} else if (platform === "linux") {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const OsBiometricsServiceLinux = require("./os-biometrics-linux.service").default;
|
const OsBiometricsServiceLinux = require("./os-biometrics-linux.service").default;
|
||||||
this.osBiometricsService = new OsBiometricsServiceLinux(this.i18nService, this.windowMain);
|
this.osBiometricsService = new OsBiometricsServiceLinux(
|
||||||
|
this.biometricStateService,
|
||||||
|
this.encryptService,
|
||||||
|
this.cryptoFunctionService,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unsupported platform");
|
throw new Error("Unsupported platform");
|
||||||
}
|
}
|
||||||
@@ -55,11 +63,11 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
* @returns the status of the biometrics of the platform
|
* @returns the status of the biometrics of the platform
|
||||||
*/
|
*/
|
||||||
async getBiometricsStatus(): Promise<BiometricsStatus> {
|
async getBiometricsStatus(): Promise<BiometricsStatus> {
|
||||||
if (!(await this.osBiometricsService.osSupportsBiometric())) {
|
if (!(await this.osBiometricsService.supportsBiometrics())) {
|
||||||
return BiometricsStatus.HardwareUnavailable;
|
return BiometricsStatus.HardwareUnavailable;
|
||||||
} else {
|
} else {
|
||||||
if (await this.osBiometricsService.osBiometricsNeedsSetup()) {
|
if (await this.osBiometricsService.needsSetup()) {
|
||||||
if (await this.osBiometricsService.osBiometricsCanAutoSetup()) {
|
if (await this.osBiometricsService.canAutoSetup()) {
|
||||||
return BiometricsStatus.AutoSetupNeeded;
|
return BiometricsStatus.AutoSetupNeeded;
|
||||||
} else {
|
} else {
|
||||||
return BiometricsStatus.ManualSetupNeeded;
|
return BiometricsStatus.ManualSetupNeeded;
|
||||||
@@ -80,20 +88,12 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
if (!(await this.biometricStateService.getBiometricUnlockEnabled(userId))) {
|
if (!(await this.biometricStateService.getBiometricUnlockEnabled(userId))) {
|
||||||
return BiometricsStatus.NotEnabledLocally;
|
return BiometricsStatus.NotEnabledLocally;
|
||||||
}
|
}
|
||||||
|
|
||||||
const platformStatus = await this.getBiometricsStatus();
|
const platformStatus = await this.getBiometricsStatus();
|
||||||
if (!(platformStatus === BiometricsStatus.Available)) {
|
if (!(platformStatus === BiometricsStatus.Available)) {
|
||||||
return platformStatus;
|
return platformStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
return await this.osBiometricsService.getBiometricsFirstUnlockStatusForUser(userId);
|
||||||
const clientKeyHalfB64 = this.clientKeyHalves.get(userId);
|
|
||||||
const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64;
|
|
||||||
if (!clientKeyHalfSatisfied) {
|
|
||||||
return BiometricsStatus.UnlockNeeded;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BiometricsStatus.Available;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticateBiometric(): Promise<boolean> {
|
async authenticateBiometric(): Promise<boolean> {
|
||||||
@@ -101,11 +101,7 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setupBiometrics(): Promise<void> {
|
async setupBiometrics(): Promise<void> {
|
||||||
return await this.osBiometricsService.osBiometricsSetup();
|
return await this.osBiometricsService.runSetup();
|
||||||
}
|
|
||||||
|
|
||||||
async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise<void> {
|
|
||||||
this.clientKeyHalves.set(userId, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticateWithBiometrics(): Promise<boolean> {
|
async authenticateWithBiometrics(): Promise<boolean> {
|
||||||
@@ -113,43 +109,23 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async unlockWithBiometricsForUser(userId: UserId): Promise<UserKey | null> {
|
async unlockWithBiometricsForUser(userId: UserId): Promise<UserKey | null> {
|
||||||
const biometricKey = await this.osBiometricsService.getBiometricKey(
|
return (await this.osBiometricsService.getBiometricKey(userId)) as UserKey;
|
||||||
"Bitwarden_biometric",
|
|
||||||
`${userId}_user_biometric`,
|
|
||||||
this.clientKeyHalves.get(userId) ?? undefined,
|
|
||||||
);
|
|
||||||
if (biometricKey == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SymmetricCryptoKey.fromString(biometricKey) as UserKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise<void> {
|
async setBiometricProtectedUnlockKeyForUser(
|
||||||
const service = "Bitwarden_biometric";
|
userId: UserId,
|
||||||
const storageKey = `${userId}_user_biometric`;
|
key: SymmetricCryptoKey,
|
||||||
if (!this.clientKeyHalves.has(userId)) {
|
): Promise<void> {
|
||||||
throw new Error("No client key half provided for user");
|
return await this.osBiometricsService.setBiometricKey(userId, key);
|
||||||
}
|
|
||||||
|
|
||||||
return await this.osBiometricsService.setBiometricKey(
|
|
||||||
service,
|
|
||||||
storageKey,
|
|
||||||
value,
|
|
||||||
this.clientKeyHalves.get(userId) ?? undefined,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void> {
|
async deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void> {
|
||||||
return await this.osBiometricsService.deleteBiometricKey(
|
return await this.osBiometricsService.deleteBiometricKey(userId);
|
||||||
"Bitwarden_biometric",
|
|
||||||
`${userId}_user_biometric`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether to auto-prompt the user for biometric unlock; this can be used to prevent auto-prompting being initiated by a process reload.
|
* Set whether to auto-prompt the user for biometric unlock; this can be used to prevent auto-prompting being initiated by a process reload.
|
||||||
* Reasons for enabling auto prompt include: Starting the app, un-minimizing the app, manually account switching
|
* Reasons for enabling auto-prompt include: Starting the app, un-minimizing the app, manually account switching
|
||||||
* @param value Whether to auto-prompt the user for biometric unlock
|
* @param value Whether to auto-prompt the user for biometric unlock
|
||||||
*/
|
*/
|
||||||
async setShouldAutopromptNow(value: boolean): Promise<void> {
|
async setShouldAutopromptNow(value: boolean): Promise<void> {
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { biometrics, passwords } from "@bitwarden/desktop-napi";
|
import { biometrics, passwords } from "@bitwarden/desktop-napi";
|
||||||
|
import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { WindowMain } from "../../main/window.main";
|
|
||||||
import { isFlatpak, isLinux, isSnapStore } from "../../utils";
|
import { isFlatpak, isLinux, isSnapStore } from "../../utils";
|
||||||
|
|
||||||
import { OsBiometricService } from "./os-biometrics.service";
|
import { OsBiometricService } from "./os-biometrics.service";
|
||||||
@@ -28,59 +32,62 @@ const polkitPolicy = `<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
const policyFileName = "com.bitwarden.Bitwarden.policy";
|
const policyFileName = "com.bitwarden.Bitwarden.policy";
|
||||||
const policyPath = "/usr/share/polkit-1/actions/";
|
const policyPath = "/usr/share/polkit-1/actions/";
|
||||||
|
|
||||||
|
const SERVICE = "Bitwarden_biometric";
|
||||||
|
function getLookupKeyForUser(userId: UserId): string {
|
||||||
|
return `${userId}_user_biometric`;
|
||||||
|
}
|
||||||
|
|
||||||
export default class OsBiometricsServiceLinux implements OsBiometricService {
|
export default class OsBiometricsServiceLinux implements OsBiometricService {
|
||||||
constructor(
|
constructor(
|
||||||
private i18nservice: I18nService,
|
private biometricStateService: BiometricStateService,
|
||||||
private windowMain: WindowMain,
|
private encryptService: EncryptService,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
) {}
|
) {}
|
||||||
private _iv: string | null = null;
|
private _iv: string | null = null;
|
||||||
// Use getKeyMaterial helper instead of direct access
|
// Use getKeyMaterial helper instead of direct access
|
||||||
private _osKeyHalf: string | null = null;
|
private _osKeyHalf: string | null = null;
|
||||||
|
private clientKeyHalves = new Map<UserId, Uint8Array | null>();
|
||||||
|
|
||||||
async setBiometricKey(
|
async setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise<void> {
|
||||||
service: string,
|
const clientKeyPartB64 = Utils.fromBufferToB64(
|
||||||
key: string,
|
await this.getOrCreateBiometricEncryptionClientKeyHalf(userId, key),
|
||||||
value: string,
|
);
|
||||||
clientKeyPartB64: string | undefined,
|
|
||||||
): Promise<void> {
|
|
||||||
const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 });
|
const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 });
|
||||||
await biometrics.setBiometricSecret(
|
await biometrics.setBiometricSecret(
|
||||||
service,
|
SERVICE,
|
||||||
key,
|
getLookupKeyForUser(userId),
|
||||||
value,
|
key.toBase64(),
|
||||||
storageDetails.key_material,
|
storageDetails.key_material,
|
||||||
storageDetails.ivB64,
|
storageDetails.ivB64,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
async deleteBiometricKey(service: string, key: string): Promise<void> {
|
async deleteBiometricKey(userId: UserId): Promise<void> {
|
||||||
await passwords.deletePassword(service, key);
|
await passwords.deletePassword(SERVICE, getLookupKeyForUser(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBiometricKey(
|
async getBiometricKey(userId: UserId): Promise<SymmetricCryptoKey | null> {
|
||||||
service: string,
|
|
||||||
storageKey: string,
|
|
||||||
clientKeyPartB64: string | undefined,
|
|
||||||
): Promise<string | null> {
|
|
||||||
const success = await this.authenticateBiometric();
|
const success = await this.authenticateBiometric();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error("Biometric authentication failed");
|
throw new Error("Biometric authentication failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = await passwords.getPassword(service, storageKey);
|
const value = await passwords.getPassword(SERVICE, getLookupKeyForUser(userId));
|
||||||
|
|
||||||
if (value == null || value == "") {
|
if (value == null || value == "") {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
const clientKeyHalf = this.clientKeyHalves.get(userId);
|
||||||
|
const clientKeyPartB64 = Utils.fromBufferToB64(clientKeyHalf);
|
||||||
const encValue = new EncString(value);
|
const encValue = new EncString(value);
|
||||||
this.setIv(encValue.iv);
|
this.setIv(encValue.iv);
|
||||||
const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 });
|
const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 });
|
||||||
const storedValue = await biometrics.getBiometricSecret(
|
const storedValue = await biometrics.getBiometricSecret(
|
||||||
service,
|
SERVICE,
|
||||||
storageKey,
|
getLookupKeyForUser(userId),
|
||||||
storageDetails.key_material,
|
storageDetails.key_material,
|
||||||
);
|
);
|
||||||
return storedValue;
|
return SymmetricCryptoKey.fromString(storedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +96,7 @@ export default class OsBiometricsServiceLinux implements OsBiometricService {
|
|||||||
return await biometrics.prompt(hwnd, "");
|
return await biometrics.prompt(hwnd, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async osSupportsBiometric(): Promise<boolean> {
|
async supportsBiometrics(): Promise<boolean> {
|
||||||
// We assume all linux distros have some polkit implementation
|
// We assume all linux distros have some polkit implementation
|
||||||
// that either has bitwarden set up or not, which is reflected in osBiomtricsNeedsSetup.
|
// that either has bitwarden set up or not, which is reflected in osBiomtricsNeedsSetup.
|
||||||
// Snap does not have access at the moment to polkit
|
// Snap does not have access at the moment to polkit
|
||||||
@@ -99,7 +106,7 @@ export default class OsBiometricsServiceLinux implements OsBiometricService {
|
|||||||
return await passwords.isAvailable();
|
return await passwords.isAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsNeedsSetup(): Promise<boolean> {
|
async needsSetup(): Promise<boolean> {
|
||||||
if (isSnapStore()) {
|
if (isSnapStore()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -108,7 +115,7 @@ export default class OsBiometricsServiceLinux implements OsBiometricService {
|
|||||||
return !(await biometrics.available());
|
return !(await biometrics.available());
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsCanAutoSetup(): Promise<boolean> {
|
async canAutoSetup(): Promise<boolean> {
|
||||||
// We cannot auto setup on snap or flatpak since the filesystem is sandboxed.
|
// We cannot auto setup on snap or flatpak since the filesystem is sandboxed.
|
||||||
// The user needs to manually set up the polkit policy outside of the sandbox
|
// The user needs to manually set up the polkit policy outside of the sandbox
|
||||||
// since we allow access to polkit via dbus for the sandboxed clients, the authentication works from
|
// since we allow access to polkit via dbus for the sandboxed clients, the authentication works from
|
||||||
@@ -116,7 +123,7 @@ export default class OsBiometricsServiceLinux implements OsBiometricService {
|
|||||||
return isLinux() && !isSnapStore() && !isFlatpak();
|
return isLinux() && !isSnapStore() && !isFlatpak();
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsSetup(): Promise<void> {
|
async runSetup(): Promise<void> {
|
||||||
const process = spawn("pkexec", [
|
const process = spawn("pkexec", [
|
||||||
"bash",
|
"bash",
|
||||||
"-c",
|
"-c",
|
||||||
@@ -165,4 +172,46 @@ export default class OsBiometricsServiceLinux implements OsBiometricService {
|
|||||||
ivB64: this._iv,
|
ivB64: this._iv,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getOrCreateBiometricEncryptionClientKeyHalf(
|
||||||
|
userId: UserId,
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
): Promise<Uint8Array | null> {
|
||||||
|
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
||||||
|
if (!requireClientKeyHalf) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clientKeyHalves.has(userId)) {
|
||||||
|
return this.clientKeyHalves.get(userId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve existing key half if it exists
|
||||||
|
let clientKeyHalf: Uint8Array | null = null;
|
||||||
|
const encryptedClientKeyHalf =
|
||||||
|
await this.biometricStateService.getEncryptedClientKeyHalf(userId);
|
||||||
|
if (encryptedClientKeyHalf != null) {
|
||||||
|
clientKeyHalf = await this.encryptService.decryptBytes(encryptedClientKeyHalf, key);
|
||||||
|
}
|
||||||
|
if (clientKeyHalf == null) {
|
||||||
|
// Set a key half if it doesn't exist
|
||||||
|
const keyBytes = await this.cryptoFunctionService.randomBytes(32);
|
||||||
|
const encKey = await this.encryptService.encryptBytes(keyBytes, key);
|
||||||
|
await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientKeyHalves.set(userId, clientKeyHalf);
|
||||||
|
|
||||||
|
return clientKeyHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise<BiometricsStatus> {
|
||||||
|
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
||||||
|
const clientKeyHalfB64 = this.clientKeyHalves.get(userId);
|
||||||
|
const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64;
|
||||||
|
if (!clientKeyHalfSatisfied) {
|
||||||
|
return BiometricsStatus.UnlockNeeded;
|
||||||
|
}
|
||||||
|
return BiometricsStatus.Available;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import { systemPreferences } from "electron";
|
import { systemPreferences } from "electron";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { passwords } from "@bitwarden/desktop-napi";
|
import { passwords } from "@bitwarden/desktop-napi";
|
||||||
|
import { BiometricsStatus } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { OsBiometricService } from "./os-biometrics.service";
|
import { OsBiometricService } from "./os-biometrics.service";
|
||||||
|
|
||||||
|
const SERVICE = "Bitwarden_biometric";
|
||||||
|
function getLookupKeyForUser(userId: UserId): string {
|
||||||
|
return `${userId}_user_biometric`;
|
||||||
|
}
|
||||||
|
|
||||||
export default class OsBiometricsServiceMac implements OsBiometricService {
|
export default class OsBiometricsServiceMac implements OsBiometricService {
|
||||||
constructor(private i18nservice: I18nService) {}
|
constructor(private i18nservice: I18nService) {}
|
||||||
|
|
||||||
async osSupportsBiometric(): Promise<boolean> {
|
async supportsBiometrics(): Promise<boolean> {
|
||||||
return systemPreferences.canPromptTouchID();
|
return systemPreferences.canPromptTouchID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,44 +29,52 @@ export default class OsBiometricsServiceMac implements OsBiometricService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBiometricKey(service: string, key: string): Promise<string | null> {
|
async getBiometricKey(userId: UserId): Promise<SymmetricCryptoKey | null> {
|
||||||
const success = await this.authenticateBiometric();
|
const success = await this.authenticateBiometric();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error("Biometric authentication failed");
|
throw new Error("Biometric authentication failed");
|
||||||
}
|
}
|
||||||
|
const keyB64 = await passwords.getPassword(SERVICE, getLookupKeyForUser(userId));
|
||||||
|
if (keyB64 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return await passwords.getPassword(service, key);
|
return SymmetricCryptoKey.fromString(keyB64);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setBiometricKey(service: string, key: string, value: string): Promise<void> {
|
async setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise<void> {
|
||||||
if (await this.valueUpToDate(service, key, value)) {
|
if (await this.valueUpToDate(userId, key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await passwords.setPassword(service, key, value);
|
return await passwords.setPassword(SERVICE, getLookupKeyForUser(userId), key.toBase64());
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBiometricKey(service: string, key: string): Promise<void> {
|
async deleteBiometricKey(user: UserId): Promise<void> {
|
||||||
return await passwords.deletePassword(service, key);
|
return await passwords.deletePassword(SERVICE, getLookupKeyForUser(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async valueUpToDate(service: string, key: string, value: string): Promise<boolean> {
|
private async valueUpToDate(user: UserId, key: SymmetricCryptoKey): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const existing = await passwords.getPassword(service, key);
|
const existing = await passwords.getPassword(SERVICE, getLookupKeyForUser(user));
|
||||||
return existing === value;
|
return existing === key.toBase64();
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsNeedsSetup() {
|
async needsSetup() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsCanAutoSetup(): Promise<boolean> {
|
async canAutoSetup(): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsSetup(): Promise<void> {}
|
async runSetup(): Promise<void> {}
|
||||||
|
|
||||||
|
async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise<BiometricsStatus> {
|
||||||
|
return BiometricsStatus.Available;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import OsBiometricsServiceWindows from "./os-biometrics-windows.service";
|
||||||
|
|
||||||
|
jest.mock("@bitwarden/desktop-napi", () => ({
|
||||||
|
biometrics: {
|
||||||
|
available: jest.fn(),
|
||||||
|
setBiometricSecret: jest.fn(),
|
||||||
|
getBiometricSecret: jest.fn(),
|
||||||
|
deriveKeyMaterial: jest.fn(),
|
||||||
|
prompt: jest.fn(),
|
||||||
|
},
|
||||||
|
passwords: {
|
||||||
|
getPassword: jest.fn(),
|
||||||
|
deletePassword: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("OsBiometricsServiceWindows", () => {
|
||||||
|
let service: OsBiometricsServiceWindows;
|
||||||
|
let biometricStateService: BiometricStateService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const i18nService = mock<I18nService>();
|
||||||
|
const logService = mock<LogService>();
|
||||||
|
biometricStateService = mock<BiometricStateService>();
|
||||||
|
const encryptionService = mock<EncryptService>();
|
||||||
|
const cryptoFunctionService = mock<CryptoFunctionService>();
|
||||||
|
service = new OsBiometricsServiceWindows(
|
||||||
|
i18nService,
|
||||||
|
null,
|
||||||
|
logService,
|
||||||
|
biometricStateService,
|
||||||
|
encryptionService,
|
||||||
|
cryptoFunctionService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getBiometricsFirstUnlockStatusForUser", () => {
|
||||||
|
const userId = "test-user-id" as UserId;
|
||||||
|
it("should return Available when requirePasswordOnRestart is false", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(false);
|
||||||
|
const result = await service.getBiometricsFirstUnlockStatusForUser(userId);
|
||||||
|
expect(result).toBe(BiometricsStatus.Available);
|
||||||
|
});
|
||||||
|
it("should return Available when requirePasswordOnRestart is true and client key half is set", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true);
|
||||||
|
(service as any).clientKeyHalves = new Map<string, Uint8Array>();
|
||||||
|
(service as any).clientKeyHalves.set(userId, new Uint8Array([1, 2, 3, 4]));
|
||||||
|
const result = await service.getBiometricsFirstUnlockStatusForUser(userId);
|
||||||
|
expect(result).toBe(BiometricsStatus.Available);
|
||||||
|
});
|
||||||
|
it("should return UnlockNeeded when requirePasswordOnRestart is true and client key half is not set", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true);
|
||||||
|
(service as any).clientKeyHalves = new Map<string, Uint8Array>();
|
||||||
|
const result = await service.getBiometricsFirstUnlockStatusForUser(userId);
|
||||||
|
expect(result).toBe(BiometricsStatus.UnlockNeeded);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getOrCreateBiometricEncryptionClientKeyHalf", () => {
|
||||||
|
const userId = "test-user-id" as UserId;
|
||||||
|
const key = new SymmetricCryptoKey(new Uint8Array(64));
|
||||||
|
let encryptionService: EncryptService;
|
||||||
|
let cryptoFunctionService: CryptoFunctionService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
encryptionService = mock<EncryptService>();
|
||||||
|
cryptoFunctionService = mock<CryptoFunctionService>();
|
||||||
|
service = new OsBiometricsServiceWindows(
|
||||||
|
mock<I18nService>(),
|
||||||
|
null,
|
||||||
|
mock<LogService>(),
|
||||||
|
biometricStateService,
|
||||||
|
encryptionService,
|
||||||
|
cryptoFunctionService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null if getRequirePasswordOnRestart is false", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(false);
|
||||||
|
const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return cached key half if already present", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true);
|
||||||
|
const cachedKeyHalf = new Uint8Array([10, 20, 30]);
|
||||||
|
(service as any).clientKeyHalves.set(userId.toString(), cachedKeyHalf);
|
||||||
|
const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key);
|
||||||
|
expect(result).toBe(cachedKeyHalf);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should decrypt and return existing encrypted client key half", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true);
|
||||||
|
biometricStateService.getEncryptedClientKeyHalf = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(new Uint8Array([1, 2, 3]));
|
||||||
|
const decrypted = new Uint8Array([4, 5, 6]);
|
||||||
|
encryptionService.decryptBytes = jest.fn().mockResolvedValue(decrypted);
|
||||||
|
|
||||||
|
const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key);
|
||||||
|
|
||||||
|
expect(biometricStateService.getEncryptedClientKeyHalf).toHaveBeenCalledWith(userId);
|
||||||
|
expect(encryptionService.decryptBytes).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]), key);
|
||||||
|
expect(result).toEqual(decrypted);
|
||||||
|
expect((service as any).clientKeyHalves.get(userId.toString())).toEqual(decrypted);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate, encrypt, store, and cache a new key half if none exists", async () => {
|
||||||
|
biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true);
|
||||||
|
biometricStateService.getEncryptedClientKeyHalf = jest.fn().mockResolvedValue(null);
|
||||||
|
const randomBytes = new Uint8Array([7, 8, 9]);
|
||||||
|
cryptoFunctionService.randomBytes = jest.fn().mockResolvedValue(randomBytes);
|
||||||
|
const encrypted = new Uint8Array([10, 11, 12]);
|
||||||
|
encryptionService.encryptBytes = jest.fn().mockResolvedValue(encrypted);
|
||||||
|
biometricStateService.setEncryptedClientKeyHalf = jest.fn().mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key);
|
||||||
|
|
||||||
|
expect(cryptoFunctionService.randomBytes).toHaveBeenCalledWith(32);
|
||||||
|
expect(encryptionService.encryptBytes).toHaveBeenCalledWith(randomBytes, key);
|
||||||
|
expect(biometricStateService.setEncryptedClientKeyHalf).toHaveBeenCalledWith(
|
||||||
|
encrypted,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect((service as any).clientKeyHalves.get(userId.toString())).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { biometrics, passwords } from "@bitwarden/desktop-napi";
|
import { biometrics, passwords } from "@bitwarden/desktop-napi";
|
||||||
|
import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { WindowMain } from "../../main/window.main";
|
import { WindowMain } from "../../main/window.main";
|
||||||
|
|
||||||
@@ -13,87 +17,107 @@ import { OsBiometricService } from "./os-biometrics.service";
|
|||||||
const KEY_WITNESS_SUFFIX = "_witness";
|
const KEY_WITNESS_SUFFIX = "_witness";
|
||||||
const WITNESS_VALUE = "known key";
|
const WITNESS_VALUE = "known key";
|
||||||
|
|
||||||
|
const SERVICE = "Bitwarden_biometric";
|
||||||
|
function getLookupKeyForUser(userId: UserId): string {
|
||||||
|
return `${userId}_user_biometric`;
|
||||||
|
}
|
||||||
|
|
||||||
export default class OsBiometricsServiceWindows implements OsBiometricService {
|
export default class OsBiometricsServiceWindows implements OsBiometricService {
|
||||||
// Use set helper method instead of direct access
|
// Use set helper method instead of direct access
|
||||||
private _iv: string | null = null;
|
private _iv: string | null = null;
|
||||||
// Use getKeyMaterial helper instead of direct access
|
// Use getKeyMaterial helper instead of direct access
|
||||||
private _osKeyHalf: string | null = null;
|
private _osKeyHalf: string | null = null;
|
||||||
|
private clientKeyHalves = new Map<UserId, Uint8Array>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private windowMain: WindowMain,
|
private windowMain: WindowMain,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private biometricStateService: BiometricStateService,
|
||||||
|
private encryptService: EncryptService,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async osSupportsBiometric(): Promise<boolean> {
|
async supportsBiometrics(): Promise<boolean> {
|
||||||
return await biometrics.available();
|
return await biometrics.available();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBiometricKey(
|
async getBiometricKey(userId: UserId): Promise<SymmetricCryptoKey | null> {
|
||||||
service: string,
|
const value = await passwords.getPassword(SERVICE, getLookupKeyForUser(userId));
|
||||||
storageKey: string,
|
let clientKeyHalfB64: string | null = null;
|
||||||
clientKeyHalfB64: string,
|
if (this.clientKeyHalves.has(userId)) {
|
||||||
): Promise<string | null> {
|
clientKeyHalfB64 = Utils.fromBufferToB64(this.clientKeyHalves.get(userId));
|
||||||
const value = await passwords.getPassword(service, storageKey);
|
}
|
||||||
|
|
||||||
if (value == null || value == "") {
|
if (value == null || value == "") {
|
||||||
return null;
|
return null;
|
||||||
} else if (!EncString.isSerializedEncString(value)) {
|
} else if (!EncString.isSerializedEncString(value)) {
|
||||||
// Update to format encrypted with client key half
|
// Update to format encrypted with client key half
|
||||||
const storageDetails = await this.getStorageDetails({
|
const storageDetails = await this.getStorageDetails({
|
||||||
clientKeyHalfB64,
|
clientKeyHalfB64: clientKeyHalfB64,
|
||||||
});
|
});
|
||||||
|
|
||||||
await biometrics.setBiometricSecret(
|
await biometrics.setBiometricSecret(
|
||||||
service,
|
SERVICE,
|
||||||
storageKey,
|
getLookupKeyForUser(userId),
|
||||||
value,
|
value,
|
||||||
storageDetails.key_material,
|
storageDetails.key_material,
|
||||||
storageDetails.ivB64,
|
storageDetails.ivB64,
|
||||||
);
|
);
|
||||||
return value;
|
return SymmetricCryptoKey.fromString(value);
|
||||||
} else {
|
} else {
|
||||||
const encValue = new EncString(value);
|
const encValue = new EncString(value);
|
||||||
this.setIv(encValue.iv);
|
this.setIv(encValue.iv);
|
||||||
const storageDetails = await this.getStorageDetails({
|
const storageDetails = await this.getStorageDetails({
|
||||||
clientKeyHalfB64,
|
clientKeyHalfB64: clientKeyHalfB64,
|
||||||
});
|
});
|
||||||
return await biometrics.getBiometricSecret(service, storageKey, storageDetails.key_material);
|
return SymmetricCryptoKey.fromString(
|
||||||
|
await biometrics.getBiometricSecret(
|
||||||
|
SERVICE,
|
||||||
|
getLookupKeyForUser(userId),
|
||||||
|
storageDetails.key_material,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setBiometricKey(
|
async setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise<void> {
|
||||||
service: string,
|
const clientKeyHalf = await this.getOrCreateBiometricEncryptionClientKeyHalf(userId, key);
|
||||||
storageKey: string,
|
|
||||||
value: string,
|
if (
|
||||||
clientKeyPartB64: string | undefined,
|
await this.valueUpToDate({
|
||||||
): Promise<void> {
|
value: key,
|
||||||
const parsedValue = SymmetricCryptoKey.fromString(value);
|
clientKeyPartB64: Utils.fromBufferToB64(clientKeyHalf),
|
||||||
if (await this.valueUpToDate({ value: parsedValue, clientKeyPartB64, service, storageKey })) {
|
service: SERVICE,
|
||||||
|
storageKey: getLookupKeyForUser(userId),
|
||||||
|
})
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 });
|
const storageDetails = await this.getStorageDetails({
|
||||||
|
clientKeyHalfB64: Utils.fromBufferToB64(clientKeyHalf),
|
||||||
|
});
|
||||||
const storedValue = await biometrics.setBiometricSecret(
|
const storedValue = await biometrics.setBiometricSecret(
|
||||||
service,
|
SERVICE,
|
||||||
storageKey,
|
getLookupKeyForUser(userId),
|
||||||
value,
|
key.toBase64(),
|
||||||
storageDetails.key_material,
|
storageDetails.key_material,
|
||||||
storageDetails.ivB64,
|
storageDetails.ivB64,
|
||||||
);
|
);
|
||||||
const parsedStoredValue = new EncString(storedValue);
|
const parsedStoredValue = new EncString(storedValue);
|
||||||
await this.storeValueWitness(
|
await this.storeValueWitness(
|
||||||
parsedValue,
|
key,
|
||||||
parsedStoredValue,
|
parsedStoredValue,
|
||||||
service,
|
SERVICE,
|
||||||
storageKey,
|
getLookupKeyForUser(userId),
|
||||||
clientKeyPartB64,
|
Utils.fromBufferToB64(clientKeyHalf),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBiometricKey(service: string, key: string): Promise<void> {
|
async deleteBiometricKey(userId: UserId): Promise<void> {
|
||||||
await passwords.deletePassword(service, key);
|
await passwords.deletePassword(SERVICE, getLookupKeyForUser(userId));
|
||||||
await passwords.deletePassword(service, key + KEY_WITNESS_SUFFIX);
|
await passwords.deletePassword(SERVICE, getLookupKeyForUser(userId) + KEY_WITNESS_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticateBiometric(): Promise<boolean> {
|
async authenticateBiometric(): Promise<boolean> {
|
||||||
@@ -240,13 +264,58 @@ export default class OsBiometricsServiceWindows implements OsBiometricService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsNeedsSetup() {
|
async needsSetup() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsCanAutoSetup(): Promise<boolean> {
|
async canAutoSetup(): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async osBiometricsSetup(): Promise<void> {}
|
async runSetup(): Promise<void> {}
|
||||||
|
|
||||||
|
async getOrCreateBiometricEncryptionClientKeyHalf(
|
||||||
|
userId: UserId,
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
): Promise<Uint8Array | null> {
|
||||||
|
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
||||||
|
if (!requireClientKeyHalf) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clientKeyHalves.has(userId)) {
|
||||||
|
return this.clientKeyHalves.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve existing key half if it exists
|
||||||
|
let clientKeyHalf: Uint8Array | null = null;
|
||||||
|
const encryptedClientKeyHalf =
|
||||||
|
await this.biometricStateService.getEncryptedClientKeyHalf(userId);
|
||||||
|
if (encryptedClientKeyHalf != null) {
|
||||||
|
clientKeyHalf = await this.encryptService.decryptBytes(encryptedClientKeyHalf, key);
|
||||||
|
}
|
||||||
|
if (clientKeyHalf == null) {
|
||||||
|
// Set a key half if it doesn't exist
|
||||||
|
const keyBytes = await this.cryptoFunctionService.randomBytes(32);
|
||||||
|
const encKey = await this.encryptService.encryptBytes(keyBytes, key);
|
||||||
|
await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientKeyHalves.set(userId, clientKeyHalf);
|
||||||
|
|
||||||
|
return clientKeyHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise<BiometricsStatus> {
|
||||||
|
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
||||||
|
if (!requireClientKeyHalf) {
|
||||||
|
return BiometricsStatus.Available;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clientKeyHalves.has(userId)) {
|
||||||
|
return BiometricsStatus.Available;
|
||||||
|
} else {
|
||||||
|
return BiometricsStatus.UnlockNeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,28 @@
|
|||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { BiometricsStatus } from "@bitwarden/key-management";
|
||||||
|
|
||||||
export interface OsBiometricService {
|
export interface OsBiometricService {
|
||||||
osSupportsBiometric(): Promise<boolean>;
|
supportsBiometrics(): Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Check whether support for biometric unlock requires setup. This can be automatic or manual.
|
* Check whether support for biometric unlock requires setup. This can be automatic or manual.
|
||||||
*
|
*
|
||||||
* @returns true if biometrics support requires setup, false if it does not (is already setup, or did not require it in the first place)
|
* @returns true if biometrics support requires setup, false if it does not (is already setup, or did not require it in the first place)
|
||||||
*/
|
*/
|
||||||
osBiometricsNeedsSetup: () => Promise<boolean>;
|
needsSetup(): Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Check whether biometrics can be automatically setup, or requires user interaction.
|
* Check whether biometrics can be automatically setup, or requires user interaction.
|
||||||
*
|
*
|
||||||
* @returns true if biometrics support can be automatically setup, false if it requires user interaction.
|
* @returns true if biometrics support can be automatically setup, false if it requires user interaction.
|
||||||
*/
|
*/
|
||||||
osBiometricsCanAutoSetup: () => Promise<boolean>;
|
canAutoSetup(): Promise<boolean>;
|
||||||
/**
|
/**
|
||||||
* Starts automatic biometric setup, which places the required configuration files / changes the required settings.
|
* Starts automatic biometric setup, which places the required configuration files / changes the required settings.
|
||||||
*/
|
*/
|
||||||
osBiometricsSetup: () => Promise<void>;
|
runSetup(): Promise<void>;
|
||||||
authenticateBiometric(): Promise<boolean>;
|
authenticateBiometric(): Promise<boolean>;
|
||||||
getBiometricKey(
|
getBiometricKey(userId: UserId): Promise<SymmetricCryptoKey | null>;
|
||||||
service: string,
|
setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise<void>;
|
||||||
key: string,
|
deleteBiometricKey(userId: UserId): Promise<void>;
|
||||||
clientKeyHalfB64: string | undefined,
|
getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise<BiometricsStatus>;
|
||||||
): Promise<string | null>;
|
|
||||||
setBiometricKey(
|
|
||||||
service: string,
|
|
||||||
key: string,
|
|
||||||
value: string,
|
|
||||||
clientKeyHalfB64: string | undefined,
|
|
||||||
): Promise<void>;
|
|
||||||
deleteBiometricKey(service: string, key: string): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,14 @@ export class RendererBiometricsService extends DesktopBiometricsService {
|
|||||||
return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id);
|
return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise<void> {
|
async setBiometricProtectedUnlockKeyForUser(
|
||||||
return await ipc.keyManagement.biometric.setBiometricProtectedUnlockKeyForUser(userId, value);
|
userId: UserId,
|
||||||
|
value: SymmetricCryptoKey,
|
||||||
|
): Promise<void> {
|
||||||
|
return await ipc.keyManagement.biometric.setBiometricProtectedUnlockKeyForUser(
|
||||||
|
userId,
|
||||||
|
value.toBase64(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void> {
|
async deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void> {
|
||||||
@@ -46,10 +52,6 @@ export class RendererBiometricsService extends DesktopBiometricsService {
|
|||||||
return await ipc.keyManagement.biometric.setupBiometrics();
|
return await ipc.keyManagement.biometric.setupBiometrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise<void> {
|
|
||||||
return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getShouldAutopromptNow(): Promise<boolean> {
|
async getShouldAutopromptNow(): Promise<boolean> {
|
||||||
return await ipc.keyManagement.biometric.getShouldAutoprompt();
|
return await ipc.keyManagement.biometric.getShouldAutoprompt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,11 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { UserKey } from "@bitwarden/common/types/key";
|
import { UserKey } from "@bitwarden/common/types/key";
|
||||||
import { BiometricStateService, KdfConfigService } from "@bitwarden/key-management";
|
import { BiometricStateService, KdfConfigService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
makeEncString,
|
|
||||||
makeStaticByteArray,
|
|
||||||
makeSymmetricCryptoKey,
|
makeSymmetricCryptoKey,
|
||||||
FakeAccountService,
|
FakeAccountService,
|
||||||
mockAccountServiceWith,
|
mockAccountServiceWith,
|
||||||
@@ -80,7 +77,6 @@ describe("ElectronKeyService", () => {
|
|||||||
|
|
||||||
await keyService.setUserKey(userKey, mockUserId);
|
await keyService.setUserKey(userKey, mockUserId);
|
||||||
|
|
||||||
expect(biometricService.setClientKeyHalfForUser).not.toHaveBeenCalled();
|
|
||||||
expect(biometricService.setBiometricProtectedUnlockKeyForUser).not.toHaveBeenCalled();
|
expect(biometricService.setBiometricProtectedUnlockKeyForUser).not.toHaveBeenCalled();
|
||||||
expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled();
|
expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled();
|
||||||
expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId);
|
expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId);
|
||||||
@@ -96,14 +92,12 @@ describe("ElectronKeyService", () => {
|
|||||||
|
|
||||||
await keyService.setUserKey(userKey, mockUserId);
|
await keyService.setUserKey(userKey, mockUserId);
|
||||||
|
|
||||||
expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith(mockUserId, null);
|
|
||||||
expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith(
|
expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith(
|
||||||
mockUserId,
|
mockUserId,
|
||||||
userKey.keyB64,
|
userKey,
|
||||||
);
|
);
|
||||||
expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled();
|
expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled();
|
||||||
expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId);
|
expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId);
|
||||||
expect(biometricStateService.getRequirePasswordOnStart).toHaveBeenCalledWith(mockUserId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("require password on start enabled", () => {
|
describe("require password on start enabled", () => {
|
||||||
@@ -111,73 +105,11 @@ describe("ElectronKeyService", () => {
|
|||||||
biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true);
|
biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets new biometric client key half and biometric unlock key when no biometric client key half stored", async () => {
|
it("sets biometric key", async () => {
|
||||||
const clientKeyHalfBytes = makeStaticByteArray(32);
|
|
||||||
const clientKeyHalf = Utils.fromBufferToUtf8(clientKeyHalfBytes);
|
|
||||||
const encryptedClientKeyHalf = makeEncString();
|
|
||||||
biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(null);
|
|
||||||
cryptoFunctionService.randomBytes.mockResolvedValue(
|
|
||||||
clientKeyHalfBytes.buffer as CsprngArray,
|
|
||||||
);
|
|
||||||
encryptService.encryptString.mockResolvedValue(encryptedClientKeyHalf);
|
|
||||||
|
|
||||||
await keyService.setUserKey(userKey, mockUserId);
|
await keyService.setUserKey(userKey, mockUserId);
|
||||||
|
|
||||||
expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
clientKeyHalf,
|
|
||||||
);
|
|
||||||
expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith(
|
expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith(
|
||||||
mockUserId,
|
mockUserId,
|
||||||
userKey.keyB64,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.setEncryptedClientKeyHalf).toHaveBeenCalledWith(
|
|
||||||
encryptedClientKeyHalf,
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.getRequirePasswordOnStart).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.getEncryptedClientKeyHalf).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(cryptoFunctionService.randomBytes).toHaveBeenCalledWith(32);
|
|
||||||
expect(encryptService.encryptString).toHaveBeenCalledWith(clientKeyHalf, userKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets decrypted biometric client key half and biometric unlock key when existing biometric client key half stored", async () => {
|
|
||||||
const encryptedClientKeyHalf = makeEncString();
|
|
||||||
const clientKeyHalf = Utils.fromBufferToUtf8(makeStaticByteArray(32));
|
|
||||||
biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(
|
|
||||||
encryptedClientKeyHalf,
|
|
||||||
);
|
|
||||||
encryptService.decryptString.mockResolvedValue(clientKeyHalf);
|
|
||||||
|
|
||||||
await keyService.setUserKey(userKey, mockUserId);
|
|
||||||
|
|
||||||
expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
clientKeyHalf,
|
|
||||||
);
|
|
||||||
expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
userKey.keyB64,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled();
|
|
||||||
expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.getRequirePasswordOnStart).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(biometricStateService.getEncryptedClientKeyHalf).toHaveBeenCalledWith(
|
|
||||||
mockUserId,
|
|
||||||
);
|
|
||||||
expect(encryptService.decryptString).toHaveBeenCalledWith(
|
|
||||||
encryptedClientKeyHalf,
|
|
||||||
userKey,
|
userKey,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
import { KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||||
import { CsprngString } from "@bitwarden/common/types/csprng";
|
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { UserKey } from "@bitwarden/common/types/key";
|
import { UserKey } from "@bitwarden/common/types/key";
|
||||||
import {
|
import {
|
||||||
@@ -77,10 +75,7 @@ export class ElectronKeyService extends DefaultKeyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async storeBiometricsProtectedUserKey(userKey: UserKey, userId: UserId): Promise<void> {
|
private async storeBiometricsProtectedUserKey(userKey: UserKey, userId: UserId): Promise<void> {
|
||||||
// May resolve to null, in which case no client key have is required
|
await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey);
|
||||||
const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId);
|
|
||||||
await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf);
|
|
||||||
await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId: UserId): Promise<boolean> {
|
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId: UserId): Promise<boolean> {
|
||||||
@@ -91,34 +86,4 @@ export class ElectronKeyService extends DefaultKeyService {
|
|||||||
await this.biometricService.deleteBiometricUnlockKeyForUser(userId);
|
await this.biometricService.deleteBiometricUnlockKeyForUser(userId);
|
||||||
await super.clearAllStoredUserKeys(userId);
|
await super.clearAllStoredUserKeys(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getBiometricEncryptionClientKeyHalf(
|
|
||||||
userKey: UserKey,
|
|
||||||
userId: UserId,
|
|
||||||
): Promise<CsprngString | null> {
|
|
||||||
const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId);
|
|
||||||
if (!requireClientKeyHalf) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve existing key half if it exists
|
|
||||||
let clientKeyHalf: CsprngString | null = null;
|
|
||||||
const encryptedClientKeyHalf =
|
|
||||||
await this.biometricStateService.getEncryptedClientKeyHalf(userId);
|
|
||||||
if (encryptedClientKeyHalf != null) {
|
|
||||||
clientKeyHalf = (await this.encryptService.decryptString(
|
|
||||||
encryptedClientKeyHalf,
|
|
||||||
userKey,
|
|
||||||
)) as CsprngString;
|
|
||||||
}
|
|
||||||
if (clientKeyHalf == null) {
|
|
||||||
// Set a key half if it doesn't exist
|
|
||||||
const keyBytes = await this.cryptoFunctionService.randomBytes(32);
|
|
||||||
clientKeyHalf = Utils.fromBufferToUtf8(keyBytes) as CsprngString;
|
|
||||||
const encKey = await this.encryptService.encryptString(clientKeyHalf, userKey);
|
|
||||||
await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientKeyHalf;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ const biometric = {
|
|||||||
action: BiometricAction.GetStatusForUser,
|
action: BiometricAction.GetStatusForUser,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
} satisfies BiometricMessage),
|
} satisfies BiometricMessage),
|
||||||
setBiometricProtectedUnlockKeyForUser: (userId: string, value: string): Promise<void> =>
|
setBiometricProtectedUnlockKeyForUser: (userId: string, keyB64: string): Promise<void> => {
|
||||||
ipcRenderer.invoke("biometric", {
|
return ipcRenderer.invoke("biometric", {
|
||||||
action: BiometricAction.SetKeyForUser,
|
action: BiometricAction.SetKeyForUser,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
key: value,
|
key: keyB64,
|
||||||
} satisfies BiometricMessage),
|
} satisfies BiometricMessage);
|
||||||
|
},
|
||||||
deleteBiometricUnlockKeyForUser: (userId: string): Promise<void> =>
|
deleteBiometricUnlockKeyForUser: (userId: string): Promise<void> =>
|
||||||
ipcRenderer.invoke("biometric", {
|
ipcRenderer.invoke("biometric", {
|
||||||
action: BiometricAction.RemoveKeyForUser,
|
action: BiometricAction.RemoveKeyForUser,
|
||||||
@@ -40,12 +41,6 @@ const biometric = {
|
|||||||
ipcRenderer.invoke("biometric", {
|
ipcRenderer.invoke("biometric", {
|
||||||
action: BiometricAction.Setup,
|
action: BiometricAction.Setup,
|
||||||
} satisfies BiometricMessage),
|
} satisfies BiometricMessage),
|
||||||
setClientKeyHalf: (userId: string, value: string | null): Promise<void> =>
|
|
||||||
ipcRenderer.invoke("biometric", {
|
|
||||||
action: BiometricAction.SetClientKeyHalf,
|
|
||||||
userId: userId,
|
|
||||||
key: value,
|
|
||||||
} satisfies BiometricMessage),
|
|
||||||
getShouldAutoprompt: (): Promise<boolean> =>
|
getShouldAutoprompt: (): Promise<boolean> =>
|
||||||
ipcRenderer.invoke("biometric", {
|
ipcRenderer.invoke("biometric", {
|
||||||
action: BiometricAction.GetShouldAutoprompt,
|
action: BiometricAction.GetShouldAutoprompt,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Subject, firstValueFrom } from "rxjs";
|
|||||||
import { SsoUrlService } from "@bitwarden/auth/common";
|
import { SsoUrlService } from "@bitwarden/auth/common";
|
||||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { ClientType } from "@bitwarden/common/enums";
|
import { ClientType } from "@bitwarden/common/enums";
|
||||||
|
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
|
||||||
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { Message, MessageSender } from "@bitwarden/common/platform/messaging";
|
import { Message, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
// eslint-disable-next-line no-restricted-imports -- For dependency creation
|
// eslint-disable-next-line no-restricted-imports -- For dependency creation
|
||||||
@@ -187,14 +188,19 @@ export class Main {
|
|||||||
|
|
||||||
this.desktopSettingsService = new DesktopSettingsService(stateProvider);
|
this.desktopSettingsService = new DesktopSettingsService(stateProvider);
|
||||||
const biometricStateService = new DefaultBiometricStateService(stateProvider);
|
const biometricStateService = new DefaultBiometricStateService(stateProvider);
|
||||||
|
const encryptService = new EncryptServiceImplementation(
|
||||||
|
this.mainCryptoFunctionService,
|
||||||
|
this.logService,
|
||||||
|
true,
|
||||||
|
);
|
||||||
this.biometricsService = new MainBiometricsService(
|
this.biometricsService = new MainBiometricsService(
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.windowMain,
|
this.windowMain,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.messagingService,
|
|
||||||
process.platform,
|
process.platform,
|
||||||
biometricStateService,
|
biometricStateService,
|
||||||
|
encryptService,
|
||||||
|
this.mainCryptoFunctionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.windowMain = new WindowMain(
|
this.windowMain = new WindowMain(
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ export enum BiometricAction {
|
|||||||
SetKeyForUser = "setKeyForUser",
|
SetKeyForUser = "setKeyForUser",
|
||||||
RemoveKeyForUser = "removeKeyForUser",
|
RemoveKeyForUser = "removeKeyForUser",
|
||||||
|
|
||||||
SetClientKeyHalf = "setClientKeyHalf",
|
|
||||||
|
|
||||||
Setup = "setup",
|
Setup = "setup",
|
||||||
|
|
||||||
GetShouldAutoprompt = "getShouldAutoprompt",
|
GetShouldAutoprompt = "getShouldAutoprompt",
|
||||||
@@ -18,21 +16,13 @@ export enum BiometricAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type BiometricMessage =
|
export type BiometricMessage =
|
||||||
| {
|
|
||||||
action: BiometricAction.SetClientKeyHalf;
|
|
||||||
userId: string;
|
|
||||||
key: string | null;
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
action: BiometricAction.SetKeyForUser;
|
action: BiometricAction.SetKeyForUser;
|
||||||
userId: string;
|
userId: string;
|
||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
action: Exclude<
|
action: Exclude<BiometricAction, BiometricAction.SetKeyForUser>;
|
||||||
BiometricAction,
|
|
||||||
BiometricAction.SetClientKeyHalf | BiometricAction.SetKeyForUser
|
|
||||||
>;
|
|
||||||
userId?: string;
|
userId?: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user