Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService {
userId: UserId,
value: SymmetricCryptoKey,
): Promise<void> {}
async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise<void> {}
async hasPersistentKey(userId: UserId): Promise<boolean> {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,9 @@ export class ForegroundBrowserBiometricsService extends BiometricsService {
userId: UserId,
value: SymmetricCryptoKey,
): Promise<void> {}

async enrollPersistent(userId: UserId): Promise<void> {}
async hasPersistentKey(userId: UserId): Promise<boolean> {
return false;
}
}
4 changes: 4 additions & 0 deletions apps/cli/src/key-management/cli-biometrics-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ export class CliBiometricsService extends BiometricsService {
userId: UserId,
value: SymmetricCryptoKey,
): Promise<void> {}
async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise<void> {}
async hasPersistentKey(userId: UserId): Promise<boolean> {
return false;
}
}
5 changes: 5 additions & 0 deletions apps/cli/src/service-container/service-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,11 @@ export class ServiceContainer {
this.configService,
this.masterPasswordService,
this.syncService,
this.keyService,
new CliBiometricsService(),
this.biometricStateService,
this.platformUtilsService,
this.sdkService,
);
}

Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/app/services/services.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: BiometricsService,
useClass: RendererBiometricsService,
deps: [TokenService],
deps: [TokenService, BiometricStateService],
}),
safeProvider({
provide: DesktopBiometricsService,
useClass: RendererBiometricsService,
deps: [TokenService],
deps: [TokenService, BiometricStateService],
}),
safeProvider({
provide: DeviceManagementComponentServiceAbstraction,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { UserId } from "@bitwarden/common/types/guid";
import { BiometricsService } from "@bitwarden/key-management";

Expand All @@ -9,8 +8,6 @@ import { BiometricsService } from "@bitwarden/key-management";
export abstract class DesktopBiometricsService extends BiometricsService {
abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void>;
abstract setupBiometrics(): Promise<void>;
abstract enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise<void>;
abstract hasPersistentKey(userId: UserId): Promise<boolean>;
/* Enables the v2 biometrics re-write. This will stay enabled until the application is restarted. */
abstract enableLinuxV2Biometrics(): Promise<void>;
abstract isLinuxV2BiometricsEnabled(): Promise<boolean>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ jest.mock("@bitwarden/desktop-napi", () => {
};
});

jest.mock("@bitwarden/common/platform/abstractions/sdk/sdk-load.service", () => ({
SdkLoadService: {
Ready: Promise.resolve(),
},
}));

jest.mock("@bitwarden/sdk-internal", () => ({
CryptoClient: {
get_key_id_for_symmetric_key: jest.fn().mockReturnValue(null),
},
}));

jest.mock("./native-v2", () => ({
WindowsBiometricsSystem: jest.fn(),
biometrics_v2: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class MainBiometricsService extends DesktopBiometricsService {
}

async deleteBiometricUnlockKeyForUser(userId: UserId): Promise<void> {
await this.biometricStateService.setBiometricEnrolledKeyId(userId, null);
return await this.osBiometricsService.deleteBiometricKey(userId);
}

Expand All @@ -142,8 +143,12 @@ export class MainBiometricsService extends DesktopBiometricsService {
return true;
}

/**
* Please note, this should only be called via the renderer service, and never directly.
* This is missing the setting of the key-id.
*/
async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise<void> {
return await this.osBiometricsService.enrollPersistent(userId, key);
await this.osBiometricsService.enrollPersistent(userId, key);
}

async hasPersistentKey(userId: UserId): Promise<boolean> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";

import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
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 { UserKey } from "@bitwarden/common/types/key";
import { BiometricsStatus } from "@bitwarden/key-management";
import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management";
import { CryptoClient } from "@bitwarden/sdk-internal";

import { DesktopBiometricsService } from "./desktop.biometrics.service";

Expand All @@ -15,7 +18,10 @@ import { DesktopBiometricsService } from "./desktop.biometrics.service";
*/
@Injectable()
export class RendererBiometricsService extends DesktopBiometricsService {
constructor(private tokenService: TokenService) {
constructor(
private tokenService: TokenService,
private biometricStateService: BiometricStateService,
) {
super();
}

Expand Down Expand Up @@ -80,7 +86,15 @@ export class RendererBiometricsService extends DesktopBiometricsService {
}

async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise<void> {
return await ipc.keyManagement.biometric.enrollPersistent(userId, key.toBase64());
await ipc.keyManagement.biometric.enrollPersistent(userId, key.toBase64());
await SdkLoadService.Ready;
const keyId = CryptoClient.get_key_id_for_symmetric_key(key.toEncoded());
if (keyId != null) {
await this.biometricStateService.setBiometricEnrolledKeyId(
userId,
Utils.fromBufferToB64(keyId),
);
}
}

async hasPersistentKey(userId: UserId): Promise<boolean> {
Expand Down
35 changes: 34 additions & 1 deletion apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ import { app } from "electron";
import { Subject, firstValueFrom } from "rxjs";

import { SsoUrlService } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/default-active-user.accessor";
import { ClientType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { Message, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- For dependency creation
Expand All @@ -22,7 +27,13 @@ import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/d
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { DefaultBiometricStateService } from "@bitwarden/key-management";
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
import {
DefaultBiometricStateService,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
import {
DefaultActiveUserStateProvider,
Expand Down Expand Up @@ -96,6 +107,14 @@ export class Main {
shell: SafeShell;
sshAgentService: MainSshAgentService;
sdkLoadService: SdkLoadService;
sdkService: DefaultSdkService;
platformUtilsService: PlatformUtilsService;
accountService: AccountService;
kdfConfigService: KdfConfigService;
keyService: KeyService;
accountCryptographicStateService: AccountCryptographicStateService;
apiService: ApiService;
configService: ConfigService;
mainDesktopAutotypeService: MainDesktopAutotypeService;
ssoCookieMain: SsoCookieMain;

Expand Down Expand Up @@ -166,6 +185,7 @@ export class Main {

this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);

const sdkClientFactory = new DefaultSdkClientFactory();
this.sdkLoadService = new MainSdkLoadService();

this.mainCryptoFunctionService = new NodeCryptoFunctionService();
Expand Down Expand Up @@ -233,6 +253,19 @@ export class Main {
(win) => this.trayMain.setupWindowListeners(win),
);

this.sdkService = new DefaultSdkService(
sdkClientFactory,
this.environmentService,
this.platformUtilsService,
this.accountService,
this.kdfConfigService,
this.keyService,
this.accountCryptographicStateService,
this.apiService,
stateProvider,
this.configService,
);

this.biometricsService = new MainBiometricsService(
this.i18nService,
this.windowMain,
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/app/key-management/web-biometric.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ export class WebBiometricsService extends BiometricsService {
userId: UserId,
value: SymmetricCryptoKey,
): Promise<void> {}
async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise<void> {}
async hasPersistentKey(userId: UserId): Promise<boolean> {
return false;
}
}
5 changes: 5 additions & 0 deletions libs/angular/src/services/jslib-services.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ const safeProviders: SafeProvider[] = [
ConfigService,
MasterPasswordServiceAbstraction,
SyncService,
KeyService,
BiometricsService,
BiometricStateService,
PlatformUtilsServiceAbstraction,
SdkService,
],
}),
safeProvider({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { mock } from "jest-mock-extended";

// eslint-disable-next-line no-restricted-imports
import { KdfConfigService } from "@bitwarden/key-management";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
// eslint-disable-next-line
import {
BiometricStateService,
BiometricsService,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import { LogService } from "@bitwarden/logging";

import { ClientType } from "../../enums";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { SyncService } from "../../platform/sync";
import { UserId } from "../../types/guid";
import { ChangeKdfService } from "../kdf/change-kdf.service.abstraction";
import { MasterPasswordServiceAbstraction } from "../master-password/abstractions/master-password.service.abstraction";

import { DefaultEncryptedMigrator } from "./default-encrypted-migrator";
import { BiometricPersistentMigration } from "./migrations/biometric-persistent-encryption-migration";
import { EncryptedMigration } from "./migrations/encrypted-migration";
import { MinimumKdfMigration } from "./migrations/minimum-kdf-migration";

jest.mock("./migrations/minimum-kdf-migration");
jest.mock("./migrations/biometric-persistent-encryption-migration");

describe("EncryptedMigrator", () => {
const mockKdfConfigService = mock<KdfConfigService>();
Expand All @@ -23,9 +33,15 @@ describe("EncryptedMigrator", () => {
const configService = mock<ConfigService>();
const masterPasswordService = mock<MasterPasswordServiceAbstraction>();
const syncService = mock<SyncService>();
const mockKeyService = mock<KeyService>();
const mockBiometricsService = mock<BiometricsService>();
const mockBiometricStateService = mock<BiometricStateService>();
const mockPlatformUtilsService = mock<PlatformUtilsService>();
const mockSdkService = mock<SdkService>();

let sut: DefaultEncryptedMigrator;
const mockMigration = mock<MinimumKdfMigration>();
const mockBiometricMigration = mock<BiometricPersistentMigration>();

const mockUserId = "00000000-0000-0000-0000-000000000000" as UserId;
const mockMasterPassword = "masterPassword123";
Expand All @@ -37,6 +53,15 @@ describe("EncryptedMigrator", () => {
(MinimumKdfMigration as jest.MockedClass<typeof MinimumKdfMigration>).mockImplementation(
() => mockMigration,
);
(
BiometricPersistentMigration as jest.MockedClass<typeof BiometricPersistentMigration>
).mockImplementation(() => mockBiometricMigration);

// Default biometric migration to no-op so it doesn't interfere with KDF migration tests
mockBiometricMigration.needsMigration.mockResolvedValue("noMigrationNeeded");

// Biometric migration is only registered on desktop
mockPlatformUtilsService.getClientType.mockReturnValue(ClientType.Desktop);

sut = new DefaultEncryptedMigrator(
mockKdfConfigService,
Expand All @@ -45,6 +70,11 @@ describe("EncryptedMigrator", () => {
configService,
masterPasswordService,
syncService,
mockKeyService,
mockBiometricsService,
mockBiometricStateService,
mockPlatformUtilsService,
mockSdkService,
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
// eslint-disable-next-line no-restricted-imports
import { KdfConfigService } from "@bitwarden/key-management";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
// eslint-disable-next-line
import {
BiometricStateService,
BiometricsService,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import { LogService } from "@bitwarden/logging";

import { assertNonNullish } from "../../auth/utils";
import { ClientType } from "../../enums";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { SyncService } from "../../platform/sync";
import { UserId } from "../../types/guid";
import { ChangeKdfService } from "../kdf/change-kdf.service.abstraction";
import { MasterPasswordServiceAbstraction } from "../master-password/abstractions/master-password.service.abstraction";

import { EncryptedMigrator } from "./encrypted-migrator.abstraction";
import { BiometricPersistentMigration } from "./migrations/biometric-persistent-encryption-migration";
import { EncryptedMigration, MigrationRequirement } from "./migrations/encrypted-migration";
import { MinimumKdfMigration } from "./migrations/minimum-kdf-migration";

Expand All @@ -18,12 +27,17 @@ export class DefaultEncryptedMigrator implements EncryptedMigrator {
private isRunningMigration = false;

constructor(
readonly kdfConfigService: KdfConfigService,
readonly changeKdfService: ChangeKdfService,
kdfConfigService: KdfConfigService,
changeKdfService: ChangeKdfService,
private readonly logService: LogService,
readonly configService: ConfigService,
readonly masterPasswordService: MasterPasswordServiceAbstraction,
readonly syncService: SyncService,
configService: ConfigService,
masterPasswordService: MasterPasswordServiceAbstraction,
private readonly syncService: SyncService,
keyService: KeyService,
biometricsService: BiometricsService,
biometricStateService: BiometricStateService,
platformUtilsService: PlatformUtilsService,
sdkService: SdkService,
) {
// Register migrations here
this.migrations.push({
Expand All @@ -37,6 +51,20 @@ export class DefaultEncryptedMigrator implements EncryptedMigrator {
syncService,
),
});

// Biometric persistent encryption is only relevant on desktop
if (platformUtilsService.getClientType() === ClientType.Desktop) {
this.migrations.push({
name: "Biometric V2 Encryption Migration",
migration: new BiometricPersistentMigration(
keyService,
biometricsService,
biometricStateService,
logService,
sdkService,
),
});
}
}

async runMigrations(userId: UserId, masterPassword: string | null): Promise<void> {
Expand Down
Loading
Loading