1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[PM-6211] Create key generation service (#7939)

* create key generation service

* replace old key generation service and add references

* use key generation service in key connector service

* use key generation service in send service

* user key generation service in access service

* use key generation service in device trust service

* fix tests

* fix browser

* add createKeyFromMaterial and tests

* create ephemeral key

* fix tests

* rename method and add returns docs

* ignore material in destructure

* modify test

* specify material as key material

* pull out magic strings to properties

* make salt optional and generate if not provided

* fix test

* fix parameters

* update docs to include link to HKDF rfc
This commit is contained in:
Jake Fink
2024-02-23 08:48:15 -05:00
committed by GitHub
parent 071959317c
commit 19a373d87e
27 changed files with 401 additions and 149 deletions

View File

@@ -30,6 +30,10 @@ import {
I18nServiceInitOptions,
i18nServiceFactory,
} from "../../../platform/background/service-factories/i18n-service.factory";
import {
KeyGenerationServiceInitOptions,
keyGenerationServiceFactory,
} from "../../../platform/background/service-factories/key-generation-service.factory";
import {
PlatformUtilsServiceInitOptions,
platformUtilsServiceFactory,
@@ -42,6 +46,7 @@ import {
type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions;
export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactoryOptions &
KeyGenerationServiceInitOptions &
CryptoFunctionServiceInitOptions &
CryptoServiceInitOptions &
EncryptServiceInitOptions &
@@ -61,6 +66,7 @@ export function deviceTrustCryptoServiceFactory(
opts,
async () =>
new DeviceTrustCryptoService(
await keyGenerationServiceFactory(cache, opts),
await cryptoFunctionServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts),
await encryptServiceFactory(cache, opts),

View File

@@ -9,10 +9,6 @@ import {
apiServiceFactory,
ApiServiceInitOptions,
} from "../../../platform/background/service-factories/api-service.factory";
import {
CryptoFunctionServiceInitOptions,
cryptoFunctionServiceFactory,
} from "../../../platform/background/service-factories/crypto-function-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
@@ -22,6 +18,10 @@ import {
CachedServices,
factory,
} from "../../../platform/background/service-factories/factory-options";
import {
KeyGenerationServiceInitOptions,
keyGenerationServiceFactory,
} from "../../../platform/background/service-factories/key-generation-service.factory";
import {
logServiceFactory,
LogServiceInitOptions,
@@ -46,7 +46,7 @@ export type KeyConnectorServiceInitOptions = KeyConnectorServiceFactoryOptions &
TokenServiceInitOptions &
LogServiceInitOptions &
OrganizationServiceInitOptions &
CryptoFunctionServiceInitOptions;
KeyGenerationServiceInitOptions;
export function keyConnectorServiceFactory(
cache: { keyConnectorService?: AbstractKeyConnectorService } & CachedServices,
@@ -64,7 +64,7 @@ export function keyConnectorServiceFactory(
await tokenServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
await organizationServiceFactory(cache, opts),
await cryptoFunctionServiceFactory(cache, opts),
await keyGenerationServiceFactory(cache, opts),
opts.keyConnectorServiceOptions.logoutCallback,
),
);

View File

@@ -54,6 +54,7 @@ import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/pla
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -72,6 +73,7 @@ import { ContainerService } from "@bitwarden/common/platform/services/container.
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
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";
@@ -180,7 +182,6 @@ import BrowserMessagingPrivateModeBackgroundService from "../platform/services/b
import BrowserMessagingService from "../platform/services/browser-messaging.service";
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
import { BrowserStateService } from "../platform/services/browser-state.service";
import { KeyGenerationService } from "../platform/services/key-generation.service";
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
@@ -207,6 +208,7 @@ export default class MainBackground {
i18nService: I18nServiceAbstraction;
platformUtilsService: PlatformUtilsServiceAbstraction;
logService: LogServiceAbstraction;
keyGenerationService: KeyGenerationServiceAbstraction;
cryptoService: CryptoServiceAbstraction;
cryptoFunctionService: CryptoFunctionServiceAbstraction;
tokenService: TokenServiceAbstraction;
@@ -326,6 +328,7 @@ export default class MainBackground {
? new BrowserMessagingPrivateModeBackgroundService()
: new BrowserMessagingService();
this.logService = new ConsoleLogService(false);
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
this.cryptoFunctionService = new WebCryptoFunctionService(self);
this.storageService = new BrowserLocalStorageService();
this.secureStorageService = new BrowserLocalStorageService();
@@ -333,14 +336,14 @@ export default class MainBackground {
BrowserApi.manifestVersion === 3
? new LocalBackedSessionStorageService(
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
new KeyGenerationService(this.cryptoFunctionService),
this.keyGenerationService,
)
: new MemoryStorageService();
this.memoryStorageForStateProviders =
BrowserApi.manifestVersion === 3
? new LocalBackedSessionStorageService(
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
new KeyGenerationService(this.cryptoFunctionService),
this.keyGenerationService,
)
: new BackgroundMemoryStorageService();
this.globalStateProvider = new DefaultGlobalStateProvider(
@@ -426,6 +429,7 @@ export default class MainBackground {
);
this.i18nService = new BrowserI18nService(BrowserApi.getUILanguage(), this.stateService);
this.cryptoService = new BrowserCryptoService(
this.keyGenerationService,
this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService,
@@ -478,7 +482,7 @@ export default class MainBackground {
this.tokenService,
this.logService,
this.organizationService,
this.cryptoFunctionService,
this.keyGenerationService,
logoutCallback,
);
@@ -506,6 +510,7 @@ export default class MainBackground {
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
this.keyGenerationService,
this.cryptoFunctionService,
this.cryptoService,
this.encryptService,
@@ -636,7 +641,7 @@ export default class MainBackground {
this.sendService = new BrowserSendService(
this.cryptoService,
this.i18nService,
this.cryptoFunctionService,
this.keyGenerationService,
this.stateService,
);
this.sendApiService = new SendApiService(

View File

@@ -1,6 +1,5 @@
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { cryptoFunctionServiceFactory } from "../../platform/background/service-factories/crypto-function-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
@@ -14,6 +13,10 @@ import {
i18nServiceFactory,
I18nServiceInitOptions,
} from "../../platform/background/service-factories/i18n-service.factory";
import {
KeyGenerationServiceInitOptions,
keyGenerationServiceFactory,
} from "../../platform/background/service-factories/key-generation-service.factory";
import {
stateServiceFactory,
StateServiceInitOptions,
@@ -25,6 +28,7 @@ type SendServiceFactoryOptions = FactoryOptions;
export type SendServiceInitOptions = SendServiceFactoryOptions &
CryptoServiceInitOptions &
I18nServiceInitOptions &
KeyGenerationServiceInitOptions &
StateServiceInitOptions;
export function sendServiceFactory(
@@ -39,7 +43,7 @@ export function sendServiceFactory(
new BrowserSendService(
await cryptoServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts),
await cryptoFunctionServiceFactory(cache, opts),
await keyGenerationServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
),
);

View File

@@ -20,6 +20,10 @@ import {
} from "./crypto-function-service.factory";
import { encryptServiceFactory, EncryptServiceInitOptions } from "./encrypt-service.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import {
KeyGenerationServiceInitOptions,
keyGenerationServiceFactory,
} from "./key-generation-service.factory";
import {
PlatformUtilsServiceInitOptions,
platformUtilsServiceFactory,
@@ -29,6 +33,7 @@ import { StateProviderInitOptions, stateProviderFactory } from "./state-provider
type CryptoServiceFactoryOptions = FactoryOptions;
export type CryptoServiceInitOptions = CryptoServiceFactoryOptions &
KeyGenerationServiceInitOptions &
CryptoFunctionServiceInitOptions &
EncryptServiceInitOptions &
PlatformUtilsServiceInitOptions &
@@ -47,6 +52,7 @@ export function cryptoServiceFactory(
opts,
async () =>
new BrowserCryptoService(
await keyGenerationServiceFactory(cache, opts),
await cryptoFunctionServiceFactory(cache, opts),
await encryptServiceFactory(cache, opts),
await platformUtilsServiceFactory(cache, opts),

View File

@@ -1,4 +1,5 @@
import { KeyGenerationService } from "../../services/key-generation.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
import {
cryptoFunctionServiceFactory,
@@ -12,9 +13,9 @@ export type KeyGenerationServiceInitOptions = KeyGenerationServiceFactoryOptions
CryptoFunctionServiceInitOptions;
export function keyGenerationServiceFactory(
cache: { keyGenerationService?: KeyGenerationService } & CachedServices,
cache: { keyGenerationService?: KeyGenerationServiceAbstraction } & CachedServices,
opts: KeyGenerationServiceInitOptions,
): Promise<KeyGenerationService> {
): Promise<KeyGenerationServiceAbstraction> {
return factory(
cache,
"keyGenerationService",

View File

@@ -1,5 +0,0 @@
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
export interface AbstractKeyGenerationService {
makeEphemeralKey(numBytes?: number): Promise<SymmetricCryptoKey>;
}

View File

@@ -1,20 +0,0 @@
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { AbstractKeyGenerationService } from "./abstractions/abstract-key-generation.service";
export class KeyGenerationService implements AbstractKeyGenerationService {
constructor(private cryptoFunctionService: CryptoFunctionService) {}
async makeEphemeralKey(numBytes = 16): Promise<SymmetricCryptoKey> {
const keyMaterial = await this.cryptoFunctionService.randomBytes(numBytes);
const key = await this.cryptoFunctionService.hkdf(
keyMaterial,
"bitwarden-ephemeral",
"ephemeral",
64,
"sha256",
);
return new SymmetricCryptoKey(key);
}
}

View File

@@ -1,13 +1,13 @@
import { mock, MockProxy } from "jest-mock-extended";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import BrowserLocalStorageService from "./browser-local-storage.service";
import BrowserMemoryStorageService from "./browser-memory-storage.service";
import { KeyGenerationService } from "./key-generation.service";
import { LocalBackedSessionStorageService } from "./local-backed-session-storage.service";
describe("Browser Session Storage Service", () => {
@@ -206,7 +206,11 @@ describe("Browser Session Storage Service", () => {
describe("new key creation", () => {
beforeEach(() => {
jest.spyOn(sessionStorage, "get").mockResolvedValue(null);
keyGenerationService.makeEphemeralKey.mockResolvedValue(key);
keyGenerationService.createKeyWithPurpose.mockResolvedValue({
salt: "salt",
material: null,
derivedKey: key,
});
jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
});
@@ -214,7 +218,7 @@ describe("Browser Session Storage Service", () => {
const result = await sut.getSessionEncKey();
expect(result).toStrictEqual(key);
expect(keyGenerationService.makeEphemeralKey).toBeCalledTimes(1);
expect(keyGenerationService.createKeyWithPurpose).toBeCalledTimes(1);
});
it("should store a symmetric crypto key if it makes one", async () => {

View File

@@ -2,6 +2,7 @@ import { Subject } from "rxjs";
import { Jsonify } from "type-fest";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import {
AbstractMemoryStorageService,
StorageUpdate,
@@ -13,7 +14,6 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
import { devFlag } from "../decorators/dev-flag.decorator";
import { devFlagEnabled } from "../flags";
import { AbstractKeyGenerationService } from "./abstractions/abstract-key-generation.service";
import BrowserLocalStorageService from "./browser-local-storage.service";
import BrowserMemoryStorageService from "./browser-memory-storage.service";
@@ -31,7 +31,7 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi
constructor(
private encryptService: EncryptService,
private keyGenerationService: AbstractKeyGenerationService,
private keyGenerationService: KeyGenerationService,
) {
super();
this.updates$ = this.updatesSubject.asObservable();
@@ -138,10 +138,17 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi
async getSessionEncKey(): Promise<SymmetricCryptoKey> {
let storedKey = await this.sessionStorage.get<SymmetricCryptoKey>(keys.encKey);
if (storedKey == null || Object.keys(storedKey).length == 0) {
storedKey = await this.keyGenerationService.makeEphemeralKey();
const generatedKey = await this.keyGenerationService.createKeyWithPurpose(
128,
"ephemeral",
"bitwarden-ephemeral",
);
storedKey = generatedKey.derivedKey;
await this.setSessionEncKey(storedKey);
return storedKey;
} else {
return SymmetricCryptoKey.fromJSON(storedKey);
}
return SymmetricCryptoKey.fromJSON(storedKey);
}
async setSessionEncKey(input: SymmetricCryptoKey): Promise<void> {

View File

@@ -53,6 +53,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { FileUploadService } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import {
LogService,
LogService as LogServiceAbstraction,
@@ -358,13 +359,13 @@ function getBgService<T>(service: keyof MainBackground) {
useFactory: (
cryptoService: CryptoService,
i18nService: I18nServiceAbstraction,
cryptoFunctionService: CryptoFunctionService,
keyGenerationService: KeyGenerationService,
stateServiceAbstraction: StateServiceAbstraction,
) => {
return new BrowserSendService(
cryptoService,
i18nService,
cryptoFunctionService,
keyGenerationService,
stateServiceAbstraction,
);
},

View File

@@ -38,6 +38,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/services/user-ve
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ClientType } from "@bitwarden/common/enums";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Account } from "@bitwarden/common/platform/models/domain/account";
@@ -50,6 +51,7 @@ import { CryptoService } from "@bitwarden/common/platform/services/crypto.servic
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
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";
@@ -164,6 +166,7 @@ export class Main {
individualExportService: IndividualVaultExportServiceAbstraction;
organizationExportService: OrganizationVaultExportServiceAbstraction;
searchService: SearchService;
keyGenerationService: KeyGenerationServiceAbstraction;
cryptoFunctionService: NodeCryptoFunctionService;
encryptService: EncryptServiceImplementation;
authService: AuthService;
@@ -296,7 +299,10 @@ export class Main {
migrationRunner,
);
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
this.cryptoService = new CryptoService(
this.keyGenerationService,
this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService,
@@ -337,7 +343,7 @@ export class Main {
this.sendService = new SendService(
this.cryptoService,
this.i18nService,
this.cryptoFunctionService,
this.keyGenerationService,
this.stateService,
);
@@ -383,7 +389,7 @@ export class Main {
this.tokenService,
this.logService,
this.organizationService,
this.cryptoFunctionService,
this.keyGenerationService,
async (expired: boolean) => await this.logout(),
);
@@ -399,6 +405,7 @@ export class Main {
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
this.keyGenerationService,
this.cryptoFunctionService,
this.cryptoService,
this.encryptService,

View File

@@ -25,6 +25,7 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import {
LogService,
LogService as LogServiceAbstraction,
@@ -183,6 +184,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
KeyGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction,
EncryptService,
PlatformUtilsServiceAbstraction,

View File

@@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
@@ -23,6 +24,7 @@ import { ElectronStateService } from "./electron-state.service.abstraction";
describe("electronCryptoService", () => {
let sut: ElectronCryptoService;
const keyGenerationService = mock<KeyGenerationService>();
const cryptoFunctionService = mock<CryptoFunctionService>();
const encryptService = mock<EncryptService>();
const platformUtilService = mock<PlatformUtilsService>();
@@ -39,6 +41,7 @@ describe("electronCryptoService", () => {
stateProvider = new FakeStateProvider(accountService);
sut = new ElectronCryptoService(
keyGenerationService,
cryptoFunctionService,
encryptService,
platformUtilService,

View File

@@ -1,6 +1,7 @@
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
@@ -18,6 +19,7 @@ import { ElectronStateService } from "./electron-state.service.abstraction";
export class ElectronCryptoService extends CryptoService {
constructor(
keyGenerationService: KeyGenerationService,
cryptoFunctionService: CryptoFunctionService,
encryptService: EncryptService,
platformUtilsService: PlatformUtilsService,
@@ -28,6 +30,7 @@ export class ElectronCryptoService extends CryptoService {
private biometricStateService: BiometricStateService,
) {
super(
keyGenerationService,
cryptoFunctionService,
encryptService,
platformUtilsService,