1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-22623] Remove most TS encryption code, remove service workers (#15153)

* Add new encrypt service functions

* Undo changes

* Cleanup

* Fix build

* Fix comments

* Switch encrypt service to use SDK functions

* Move remaining functions to PureCrypto

* Tests

* Increase test coverage

* Enforce sdk.ready and drop unused codepaths

* Delete unused code

* Delete unused code

* Delete more code

* Add forgotten sdk init logic

* Fix build

* Fix cli

* Fix tests

* Fix build

* Fix browser build

* Remove compare and add more comments / warnings

* Run prettier

* Remove unused feature flags

* Add hazmat warning to aesDecrypt

* Fix build

* Fix comment

* Fix test
This commit is contained in:
Bernd Schoolmann
2025-07-30 15:50:00 +02:00
committed by GitHub
parent 2e6977e98c
commit 5dae5267d9
39 changed files with 77 additions and 706 deletions

View File

@@ -73,10 +73,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation";
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation";
import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation";
@@ -363,7 +360,6 @@ export default class MainBackground {
vaultFilterService: VaultFilterService;
usernameGenerationService: UsernameGenerationServiceAbstraction;
encryptService: EncryptService;
bulkEncryptService: FallbackBulkEncryptService;
folderApiService: FolderApiServiceAbstraction;
policyApiService: PolicyApiServiceAbstraction;
sendApiService: SendApiServiceAbstraction;
@@ -577,13 +573,11 @@ export default class MainBackground {
storageServiceProvider,
);
this.encryptService = BrowserApi.isManifestVersion(2)
? new MultithreadEncryptServiceImplementation(
this.cryptoFunctionService,
this.logService,
true,
)
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
this.encryptService = new EncryptServiceImplementation(
this.cryptoFunctionService,
this.logService,
true,
);
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
storageServiceProvider,
@@ -883,8 +877,6 @@ export default class MainBackground {
this.themeStateService = new DefaultThemeStateService(this.globalStateProvider);
this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService);
this.cipherEncryptionService = new DefaultCipherEncryptionService(
this.sdkService,
this.logService,
@@ -899,7 +891,6 @@ export default class MainBackground {
this.stateService,
this.autofillSettingsService,
this.encryptService,
this.bulkEncryptService,
this.cipherFileUploadService,
this.configService,
this.stateProvider,
@@ -1400,13 +1391,6 @@ export default class MainBackground {
// Only the "true" background should run migrations
await this.stateService.init({ runMigrations: true });
this.configService.serverConfig$.subscribe((newConfig) => {
if (newConfig != null) {
this.encryptService.onServerConfigChange(newConfig);
this.bulkEncryptService.onServerConfigChange(newConfig);
}
});
// This is here instead of in in the InitService b/c we don't plan for
// side effects to run in the Browser InitService.
const accounts = await firstValueFrom(this.accountService.accounts$);
@@ -1439,15 +1423,6 @@ export default class MainBackground {
this.syncServiceListener?.listener$().subscribe();
await this.autoSubmitLoginBackground.init();
if (
BrowserApi.isManifestVersion(2) &&
(await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService))
) {
await this.bulkEncryptService.setFeatureFlagEncryptService(
new BulkEncryptServiceImplementation(this.cryptoFunctionService, this.logService),
);
}
// If the user is logged out, switch to the next account
const active = await firstValueFrom(this.accountService.activeAccount$);
if (active != null) {

View File

@@ -3,9 +3,6 @@ import { inject, Inject, Injectable } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -30,9 +27,6 @@ export class InitService {
private themingService: AbstractThemingService,
private sdkLoadService: SdkLoadService,
private viewCacheService: PopupViewCacheService,
private configService: ConfigService,
private encryptService: EncryptService,
private bulkEncryptService: BulkEncryptService,
@Inject(DOCUMENT) private document: Document,
) {}
@@ -40,12 +34,6 @@ export class InitService {
return async () => {
await this.sdkLoadService.loadAndInit();
await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations
this.configService.serverConfig$.subscribe((newConfig) => {
if (newConfig != null) {
this.encryptService.onServerConfigChange(newConfig);
this.bulkEncryptService.onServerConfigChange(newConfig);
}
});
await this.i18nService.init();
this.twoFactorService.init();
await this.viewCacheService.init();

View File

@@ -3,7 +3,6 @@
"include": [
"src",
"../../libs/common/src/autofill/constants",
"../../libs/common/custom-matchers.d.ts",
"../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"
"../../libs/common/custom-matchers.d.ts"
]
}

View File

@@ -199,7 +199,6 @@ const mainConfig = {
"./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts",
"overlay/menu":
"./src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts",
"encrypt-worker": "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts",
"content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts",
"content/send-popup-open-message": "./src/vault/content/send-popup-open-message.ts",
},

View File

@@ -62,7 +62,6 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service";
@@ -290,7 +289,6 @@ export class ServiceContainer {
cipherAuthorizationService: CipherAuthorizationService;
ssoUrlService: SsoUrlService;
masterPasswordApiService: MasterPasswordApiServiceAbstraction;
bulkEncryptService: FallbackBulkEncryptService;
cipherEncryptionService: CipherEncryptionService;
restrictedItemTypesService: RestrictedItemTypesService;
cliRestrictedItemTypesService: CliRestrictedItemTypesService;
@@ -325,7 +323,6 @@ export class ServiceContainer {
this.logService,
true,
);
this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService);
this.storageService = new LowdbStorageService(this.logService, null, p, false, true);
this.secureStorageService = new NodeEnvSecureStorageService(
this.storageService,
@@ -719,7 +716,6 @@ export class ServiceContainer {
this.stateService,
this.autofillSettingsService,
this.encryptService,
this.bulkEncryptService,
this.cipherFileUploadService,
this.configService,
this.stateProvider,
@@ -921,12 +917,6 @@ export class ServiceContainer {
await this.sdkLoadService.loadAndInit();
await this.storageService.init();
await this.stateService.init();
this.configService.serverConfig$.subscribe((newConfig) => {
if (newConfig != null) {
this.encryptService.onServerConfigChange(newConfig);
this.bulkEncryptService.onServerConfigChange(newConfig);
}
});
this.containerService.attachToGlobal(global);
await this.i18nService.init();
this.twoFactorService.init();

View File

@@ -7,10 +7,8 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
@@ -53,8 +51,6 @@ export class InitService {
private autofillService: DesktopAutofillService,
private autotypeService: DesktopAutotypeService,
private sdkLoadService: SdkLoadService,
private configService: ConfigService,
private bulkEncryptService: BulkEncryptService,
@Inject(DOCUMENT) private document: Document,
) {}
@@ -65,13 +61,6 @@ export class InitService {
this.nativeMessagingService.init();
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
this.configService.serverConfig$.subscribe((newConfig) => {
if (newConfig != null) {
this.encryptService.onServerConfigChange(newConfig);
this.bulkEncryptService.onServerConfigChange(newConfig);
}
});
const accounts = await firstValueFrom(this.accountService.accounts$);
const setUserKeyInMemoryPromises = [];
for (const userId of Object.keys(accounts) as UserId[]) {

View File

@@ -90,8 +90,10 @@ describe("BiometricMessageHandlerService", () => {
reloadProcess: jest.fn(),
},
};
cryptoFunctionService.rsaEncrypt.mockResolvedValue(Utils.fromUtf8ToArray("encrypted"));
cryptoFunctionService.randomBytes.mockResolvedValue(new Uint8Array(64) as CsprngArray);
cryptoFunctionService.rsaEncrypt.mockResolvedValue(
Utils.fromUtf8ToArray("encrypted") as CsprngArray,
);
service = new BiometricMessageHandlerService(
cryptoFunctionService,

View File

@@ -3,6 +3,6 @@
"angularCompilerOptions": {
"strictTemplates": true
},
"include": ["src", "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"],
"include": ["src"],
"exclude": ["src/**/*.spec.ts"]
}

View File

@@ -4,12 +4,10 @@ import { MockProxy } from "jest-mock-extended";
import mock from "jest-mock-extended/lib/Mock";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -38,11 +36,9 @@ describe("EmergencyAccessService", () => {
let apiService: MockProxy<ApiService>;
let keyService: MockProxy<KeyService>;
let encryptService: MockProxy<EncryptService>;
let bulkEncryptService: MockProxy<BulkEncryptService>;
let cipherService: MockProxy<CipherService>;
let logService: MockProxy<LogService>;
let emergencyAccessService: EmergencyAccessService;
let configService: ConfigService;
const mockNewUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
const mockTrustedPublicKeys = [Utils.fromUtf8ToArray("trustedPublicKey")];
@@ -52,7 +48,6 @@ describe("EmergencyAccessService", () => {
apiService = mock<ApiService>();
keyService = mock<KeyService>();
encryptService = mock<EncryptService>();
bulkEncryptService = mock<BulkEncryptService>();
cipherService = mock<CipherService>();
logService = mock<LogService>();
@@ -61,10 +56,8 @@ describe("EmergencyAccessService", () => {
apiService,
keyService,
encryptService,
bulkEncryptService,
cipherService,
logService,
configService,
);
});

View File

@@ -3,14 +3,11 @@ import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import {
EncryptedString,
EncString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
@@ -59,10 +56,8 @@ export class EmergencyAccessService
private apiService: ApiService,
private keyService: KeyService,
private encryptService: EncryptService,
private bulkEncryptService: BulkEncryptService,
private cipherService: CipherService,
private logService: LogService,
private configService: ConfigService,
) {}
/**
@@ -258,17 +253,8 @@ export class EmergencyAccessService
)) as UserKey;
let ciphers: CipherView[] = [];
if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) {
ciphers = await this.bulkEncryptService.decryptItems(
response.ciphers.map((c) => new Cipher(c)),
grantorUserKey,
);
} else {
ciphers = await this.encryptService.decryptItems(
response.ciphers.map((c) => new Cipher(c)),
grantorUserKey,
);
}
const ciphersEncrypted = response.ciphers.map((c) => new Cipher(c));
ciphers = await Promise.all(ciphersEncrypted.map(async (c) => c.decrypt(grantorUserKey)));
return ciphers.sort(this.cipherService.getLocaleSortingFunction());
}

View File

@@ -7,10 +7,8 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
@@ -42,8 +40,6 @@ export class InitService {
private versionService: VersionService,
private ipcService: IpcService,
private sdkLoadService: SdkLoadService,
private configService: ConfigService,
private bulkEncryptService: BulkEncryptService,
private taskService: TaskService,
@Inject(DOCUMENT) private document: Document,
) {}
@@ -53,13 +49,6 @@ export class InitService {
await this.sdkLoadService.loadAndInit();
await this.stateService.init();
this.configService.serverConfig$.subscribe((newConfig) => {
if (newConfig != null) {
this.encryptService.onServerConfigChange(newConfig);
this.bulkEncryptService.onServerConfigChange(newConfig);
}
});
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (activeAccount) {
// If there is an active account, we must await the process of setting the user key in memory

View File

@@ -63,7 +63,7 @@ export class SendAccessFileComponent {
try {
const encBuf = await EncArrayBuffer.fromResponse(response);
const decBuf = await this.encryptService.decryptToBytes(encBuf, this.decKey);
const decBuf = await this.encryptService.decryptFileData(encBuf, this.decKey);
this.fileDownloadService.download({
fileName: this.send.file.fileName,
blobData: decBuf,

View File

@@ -1,8 +1,5 @@
{
"extends": "./tsconfig.json",
"files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"],
"include": [
"src/connectors/*.ts",
"../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"
]
"include": ["src/connectors/*.ts"]
}

View File

@@ -4,10 +4,5 @@
"strictTemplates": true
},
"files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"],
"include": [
"src/connectors/*.ts",
"src/**/*.stories.ts",
"src/**/*.spec.ts",
"../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"
]
"include": ["src/connectors/*.ts", "src/**/*.stories.ts", "src/**/*.spec.ts"]
}

View File

@@ -123,7 +123,7 @@ export class OrganizationAuthRequestService {
// Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.keyService.getOrgKey(organizationId);
const decOrgPrivateKey = await this.encryptService.decryptToBytes(
const decOrgPrivateKey = await this.encryptService.decryptBytes(
new EncString(encryptedOrgPrivateKey),
orgSymKey,
);

View File

@@ -7,8 +7,5 @@
"../../bitwarden_license/bit-web/src/main.ts"
],
"include": [
"../../apps/web/src/connectors/*.ts",
"../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"
]
"include": ["../../apps/web/src/connectors/*.ts"]
}

View File

@@ -11,8 +11,6 @@
"../../apps/web/src/connectors/*.ts",
"../../apps/web/src/**/*.stories.ts",
"../../apps/web/src/**/*.spec.ts",
"../../libs/common/src/key-management/crypto/services/encrypt.worker.ts",
"src/**/*.stories.ts",
"src/**/*.spec.ts"
]

View File

@@ -152,11 +152,9 @@ import { OrganizationBillingApiService } from "@bitwarden/common/billing/service
import { OrganizationSponsorshipApiService } from "@bitwarden/common/billing/services/organization/organization-sponsorship-api.service";
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
import { TaxService } from "@bitwarden/common/billing/services/tax.service";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation";
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation";
@@ -531,7 +529,6 @@ const safeProviders: SafeProvider[] = [
stateService: StateServiceAbstraction,
autofillSettingsService: AutofillSettingsServiceAbstraction,
encryptService: EncryptService,
bulkEncryptService: BulkEncryptService,
fileUploadService: CipherFileUploadServiceAbstraction,
configService: ConfigService,
stateProvider: StateProvider,
@@ -548,7 +545,6 @@ const safeProviders: SafeProvider[] = [
stateService,
autofillSettingsService,
encryptService,
bulkEncryptService,
fileUploadService,
configService,
stateProvider,
@@ -565,7 +561,6 @@ const safeProviders: SafeProvider[] = [
StateServiceAbstraction,
AutofillSettingsServiceAbstraction,
EncryptService,
BulkEncryptService,
CipherFileUploadServiceAbstraction,
ConfigService,
StateProvider,
@@ -967,14 +962,9 @@ const safeProviders: SafeProvider[] = [
}),
safeProvider({
provide: EncryptService,
useClass: MultithreadEncryptServiceImplementation,
useClass: EncryptServiceImplementation,
deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES],
}),
safeProvider({
provide: BulkEncryptService,
useClass: BulkEncryptServiceImplementation,
deps: [CryptoFunctionServiceAbstraction, LogService],
}),
safeProvider({
provide: EventUploadServiceAbstraction,
useClass: EventUploadService,

View File

@@ -86,9 +86,6 @@ describe("AuthRequestService", () => {
describe("approveOrDenyAuthRequest", () => {
beforeEach(() => {
encryptService.rsaEncrypt.mockResolvedValue({
encryptedString: "ENCRYPTED_STRING",
} as EncString);
encryptService.encapsulateKeyUnsigned.mockResolvedValue({
encryptedString: "ENCRYPTED_STRING",
} as EncString);

View File

@@ -36,9 +36,6 @@ export enum FeatureFlag {
/* Key Management */
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
UseSDKForDecryption = "use-sdk-for-decryption",
PM17987_BlockType0 = "pm-17987-block-type-0",
EnrollAeadOnKeyRotation = "enroll-aead-on-key-rotation",
ForceUpdateKDFSettings = "pm-18021-force-update-kdf-settings",
@@ -122,9 +119,6 @@ export const DefaultFeatureFlagValue = {
/* Key Management */
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
[FeatureFlag.UseSDKForDecryption]: FALSE,
[FeatureFlag.PM17987_BlockType0]: FALSE,
[FeatureFlag.EnrollAeadOnKeyRotation]: FALSE,
[FeatureFlag.ForceUpdateKDFSettings]: FALSE,

View File

@@ -1,13 +0,0 @@
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
export abstract class BulkEncryptService {
abstract decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]>;
abstract onServerConfigChange(newConfig: ServerConfig): void;
}

View File

@@ -6,12 +6,20 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr
import { CsprngArray } from "../../../types/csprng";
export abstract class CryptoFunctionService {
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract pbkdf2(
password: string | Uint8Array,
salt: string | Uint8Array,
algorithm: "sha256" | "sha512",
iterations: number,
): Promise<Uint8Array>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract hkdf(
ikm: Uint8Array,
salt: string | Uint8Array,
@@ -19,51 +27,76 @@ export abstract class CryptoFunctionService {
outputByteSize: number,
algorithm: "sha256" | "sha512",
): Promise<Uint8Array>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract hkdfExpand(
prk: Uint8Array,
info: string | Uint8Array,
outputByteSize: number,
algorithm: "sha256" | "sha512",
): Promise<Uint8Array>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract hash(
value: string | Uint8Array,
algorithm: "sha1" | "sha256" | "sha512" | "md5",
): Promise<Uint8Array>;
abstract hmac(
value: Uint8Array,
key: Uint8Array,
algorithm: "sha1" | "sha256" | "sha512",
): Promise<Uint8Array>;
abstract compare(a: Uint8Array, b: Uint8Array): Promise<boolean>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract hmacFast(
value: Uint8Array | string,
key: Uint8Array | string,
algorithm: "sha1" | "sha256" | "sha512",
): Promise<Uint8Array | string>;
abstract compareFast(a: Uint8Array | string, b: Uint8Array | string): Promise<boolean>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract aesDecryptFastParameters(
data: string,
iv: string,
mac: string,
key: SymmetricCryptoKey,
): CbcDecryptParameters<Uint8Array | string>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract aesDecryptFast({
mode,
parameters,
}:
| { mode: "cbc"; parameters: CbcDecryptParameters<Uint8Array | string> }
| { mode: "ecb"; parameters: EcbDecryptParameters<Uint8Array | string> }): Promise<string>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Only used by DDG integration until DDG uses PKCS#7 padding, and by lastpass importer.
*/
abstract aesDecrypt(
data: Uint8Array,
iv: Uint8Array,
key: Uint8Array,
mode: "cbc" | "ecb",
): Promise<Uint8Array>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract rsaEncrypt(
data: Uint8Array,
publicKey: Uint8Array,
algorithm: "sha1" | "sha256",
): Promise<Uint8Array>;
/**
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
* in the SDK instead. Further, you should probably never find yourself using this low-level crypto function.
*/
abstract rsaDecrypt(
data: Uint8Array,
privateKey: Uint8Array,
@@ -77,7 +110,6 @@ export abstract class CryptoFunctionService {
abstract aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray>;
/**
* Generates a random array of bytes of the given length. Uses a cryptographically secure random number generator.
*
* Do not use this for generating encryption keys. Use aesGenerateKey or rsaGenerateKeyPair instead.
*/
abstract randomBytes(length: number): Promise<CsprngArray>;

View File

@@ -1,51 +1,8 @@
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
import { Encrypted } from "../../../platform/interfaces/encrypted";
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { EncString } from "../models/enc-string";
export abstract class EncryptService {
/**
* @deprecated
* Decrypts an EncString to a string
* @param encString - The EncString to decrypt
* @param key - The key to decrypt the EncString with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted string
*/
abstract decryptToUtf8(
encString: EncString,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<string>;
/**
* @deprecated
* Decrypts an Encrypted object to a Uint8Array
* @param encThing - The Encrypted object to decrypt
* @param key - The key to decrypt the Encrypted object with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted Uint8Array
*/
abstract decryptToBytes(
encThing: Encrypted,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<Uint8Array | null>;
/**
* @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed
* @param items The items to decrypt
* @param key The key to decrypt the items with
*/
abstract decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]>;
/**
* Encrypts a string to an EncString
* @param plainValue - The value to encrypt
@@ -188,12 +145,6 @@ export abstract class EncryptService {
decapsulationKey: Uint8Array,
): Promise<SymmetricCryptoKey>;
/**
* @deprecated Use @see {@link encapsulateKeyUnsigned} instead
* @param data - The data to encrypt
* @param publicKey - The public key to encrypt with
*/
abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString>;
/**
* @deprecated Use @see {@link decapsulateKeyUnsigned} instead
* @param data - The ciphertext to decrypt
@@ -210,6 +161,4 @@ export abstract class EncryptService {
value: string | Uint8Array,
algorithm: "sha1" | "sha256" | "sha512",
): Promise<string>;
abstract onServerConfigChange(newConfig: ServerConfig): void;
}

View File

@@ -1,46 +0,0 @@
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { DefaultFeatureFlagValue, FeatureFlag } from "../../../enums/feature-flag.enum";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
/**
* @deprecated Will be deleted in an immediate subsequent PR
*/
export class BulkEncryptServiceImplementation implements BulkEncryptService {
protected useSDKForDecryption: boolean = DefaultFeatureFlagValue[FeatureFlag.UseSDKForDecryption];
constructor(
protected cryptoFunctionService: CryptoFunctionService,
protected logService: LogService,
) {}
/**
* Decrypts items using a web worker if the environment supports it.
* Will fall back to the main thread if the window object is not available.
*/
async decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]> {
if (key == null) {
throw new Error("No encryption key provided.");
}
if (items == null || items.length < 1) {
return [];
}
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(await items[i].decrypt(key));
}
return results;
}
onServerConfigChange(newConfig: ServerConfig): void {}
}

View File

@@ -5,15 +5,11 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-st
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PureCrypto } from "@bitwarden/sdk-internal";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { EncryptService } from "../abstractions/encrypt.service";
export class EncryptServiceImplementation implements EncryptService {
@@ -23,7 +19,6 @@ export class EncryptServiceImplementation implements EncryptService {
protected logMacFailures: boolean,
) {}
// Proxy functions; Their implementation are temporary before moving at this level to the SDK
async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise<EncString> {
if (plainValue == null) {
this.logService.warning(
@@ -171,36 +166,6 @@ export class EncryptServiceImplementation implements EncryptService {
return Utils.fromBufferToB64(hashArray);
}
// Handle updating private properties to turn on/off feature flags.
onServerConfigChange(newConfig: ServerConfig): void {}
async decryptToUtf8(
encString: EncString,
key: SymmetricCryptoKey,
_decryptContext: string = "no context",
): Promise<string> {
await SdkLoadService.Ready;
return PureCrypto.symmetric_decrypt(encString.encryptedString, key.toEncoded());
}
async decryptToBytes(
encThing: Encrypted,
key: SymmetricCryptoKey,
_decryptContext: string = "no context",
): Promise<Uint8Array | null> {
if (encThing.encryptionType == null || encThing.ivBytes == null || encThing.dataBytes == null) {
throw new Error("Cannot decrypt, missing type, IV, or data bytes.");
}
const buffer = EncArrayBuffer.fromParts(
encThing.encryptionType,
encThing.ivBytes,
encThing.dataBytes,
encThing.macBytes,
).buffer;
await SdkLoadService.Ready;
return PureCrypto.symmetric_decrypt_array_buffer(buffer, key.toEncoded());
}
async encapsulateKeyUnsigned(
sharedKey: SymmetricCryptoKey,
encapsulationKey: Uint8Array,
@@ -228,45 +193,14 @@ export class EncryptServiceImplementation implements EncryptService {
throw new Error("No decapsulationKey provided for decapsulation");
}
await SdkLoadService.Ready;
const keyBytes = PureCrypto.decapsulate_key_unsigned(
encryptedSharedKey.encryptedString,
decapsulationKey,
);
await SdkLoadService.Ready;
return new SymmetricCryptoKey(keyBytes);
}
/**
* @deprecated Replaced by BulkEncryptService (PM-4154)
*/
async decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]> {
if (items == null || items.length < 1) {
return [];
}
// don't use promise.all because this task is not io bound
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(await items[i].decrypt(key));
}
return results;
}
async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString> {
if (data == null) {
throw new Error("No data provided for encryption.");
}
if (publicKey == null) {
throw new Error("No public key provided for encryption.");
}
const encrypted = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1");
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encrypted));
}
async rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array> {
if (data == null) {
throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption.");

View File

@@ -303,12 +303,6 @@ describe("EncryptService", () => {
const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey);
expect(actual).toEqual(new EncString("encapsulated_key_unsigned"));
});
it("throws if no data was provided", () => {
return expect(encryptService.rsaEncrypt(null, new Uint8Array(32))).rejects.toThrow(
"No data provided for encryption",
);
});
});
describe("decapsulateKeyUnsigned", () => {
@@ -338,23 +332,4 @@ describe("EncryptService", () => {
expect(cryptoFunctionService.hash).toHaveBeenCalledWith("test", "sha256");
});
});
describe("decryptItems", () => {
it("returns empty array if no items are provided", async () => {
const key = mock<SymmetricCryptoKey>();
const actual = await encryptService.decryptItems(null, key);
expect(actual).toEqual([]);
});
it("returns items decrypted with provided key", async () => {
const key = mock<SymmetricCryptoKey>();
const decryptable = {
decrypt: jest.fn().mockResolvedValue("decrypted"),
};
const items = [decryptable];
const actual = await encryptService.decryptItems(items as any, key);
expect(actual).toEqual(["decrypted"]);
expect(decryptable.decrypt).toHaveBeenCalledWith(key);
});
});
});

View File

@@ -1,81 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { LogService } from "../../../platform/abstractions/log.service";
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { ConsoleLogService } from "../../../platform/services/console-log.service";
import { ContainerService } from "../../../platform/services/container.service";
import { getClassInitializer } from "../../../platform/services/cryptography/get-class-initializer";
import {
DECRYPT_COMMAND,
SET_CONFIG_COMMAND,
ParsedDecryptCommandData,
} from "../types/worker-command.type";
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
import { WebCryptoFunctionService } from "./web-crypto-function.service";
const workerApi: Worker = self as any;
let inited = false;
let encryptService: EncryptServiceImplementation;
let logService: LogService;
/**
* Bootstrap the worker environment with services required for decryption
*/
export function init() {
const cryptoFunctionService = new WebCryptoFunctionService(self);
logService = new ConsoleLogService(false);
encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true);
const bitwardenContainerService = new ContainerService(null, encryptService);
bitwardenContainerService.attachToGlobal(self);
inited = true;
}
/**
* Listen for messages and decrypt their contents
*/
workerApi.addEventListener("message", async (event: { data: string }) => {
if (!inited) {
init();
}
const request: {
command: string;
} = JSON.parse(event.data);
switch (request.command) {
case DECRYPT_COMMAND:
return await handleDecrypt(request as unknown as ParsedDecryptCommandData);
case SET_CONFIG_COMMAND: {
const newConfig = (request as unknown as { newConfig: Jsonify<ServerConfig> }).newConfig;
return await handleSetConfig(newConfig);
}
default:
logService.error(`[EncryptWorker] unknown worker command`, request.command, request);
}
});
async function handleDecrypt(request: ParsedDecryptCommandData) {
const key = SymmetricCryptoKey.fromJSON(request.key);
const items = request.items.map((jsonItem) => {
const initializer = getClassInitializer<Decryptable<any>>(jsonItem.initializerKey);
return initializer(jsonItem);
});
const result = await encryptService.decryptItems(items, key);
workerApi.postMessage({
id: request.id,
items: JSON.stringify(result),
});
}
async function handleSetConfig(newConfig: Jsonify<ServerConfig>) {
encryptService.onServerConfigChange(ServerConfig.fromJSON(newConfig));
}

View File

@@ -1,34 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { EncryptService } from "../abstractions/encrypt.service";
/**
* @deprecated Will be deleted in an immediate subsequent PR
*/
export class FallbackBulkEncryptService implements BulkEncryptService {
private featureFlagEncryptService: BulkEncryptService;
private currentServerConfig: ServerConfig | undefined = undefined;
constructor(protected encryptService: EncryptService) {}
/**
* Decrypts items using a web worker if the environment supports it.
* Will fall back to the main thread if the window object is not available.
*/
async decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]> {
return await this.encryptService.decryptItems(items, key);
}
async setFeatureFlagEncryptService(featureFlagEncryptService: BulkEncryptService) {}
onServerConfigChange(newConfig: ServerConfig): void {}
}

View File

@@ -1,27 +0,0 @@
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
/**
* @deprecated Will be deleted in an immediate subsequent PR
*/
export class MultithreadEncryptServiceImplementation extends EncryptServiceImplementation {
protected useSDKForDecryption: boolean = true;
/**
* Sends items to a web worker to decrypt them.
* This utilises multithreading to decrypt items faster without interrupting other operations (e.g. updating UI).
*/
async decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]> {
return await super.decryptItems(items, key);
}
override onServerConfigChange(newConfig: ServerConfig): void {}
}

View File

@@ -154,46 +154,6 @@ describe("WebCrypto Function Service", () => {
testHmac("sha512", Sha512Mac);
});
describe("compare", () => {
it("should successfully compare two of the same values", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const equal = await cryptoFunctionService.compare(a, a);
expect(equal).toBe(true);
});
it("should successfully compare two different values of the same length", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const equal = await cryptoFunctionService.compare(a, b);
expect(equal).toBe(false);
});
it("should successfully compare two different values of different lengths", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
const equal = await cryptoFunctionService.compare(a, b);
expect(equal).toBe(false);
});
});
describe("hmacFast", () => {
testHmacFast("sha1", Sha1Mac);
testHmacFast("sha256", Sha256Mac);
testHmacFast("sha512", Sha512Mac);
});
describe("compareFast", () => {
it("should successfully compare two of the same values", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
@@ -523,20 +483,6 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
});
}
function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey"));
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!"));
const computedMac = await cryptoFunctionService.hmacFast(
dataByteString,
keyByteString,
algorithm,
);
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac))).toBe(mac);
});
}
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
it(
"should successfully generate a " + length + " bit key pair",

View File

@@ -146,34 +146,6 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return new Uint8Array(buffer);
}
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
async compare(a: Uint8Array, b: Uint8Array): Promise<boolean> {
const macKey = await this.randomBytes(32);
const signingAlgorithm = {
name: "HMAC",
hash: { name: "SHA-256" },
};
const impKey = await this.subtle.importKey("raw", macKey, signingAlgorithm, false, ["sign"]);
const mac1 = await this.subtle.sign(signingAlgorithm, impKey, a);
const mac2 = await this.subtle.sign(signingAlgorithm, impKey, b);
if (mac1.byteLength !== mac2.byteLength) {
return false;
}
const arr1 = new Uint8Array(mac1);
const arr2 = new Uint8Array(mac2);
for (let i = 0; i < arr2.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
hmacFast(value: string, key: string, algorithm: "sha1" | "sha256" | "sha512"): Promise<string> {
const hmac = forge.hmac.create();
hmac.start(algorithm, key);
@@ -182,6 +154,9 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return Promise.resolve(bytes);
}
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
async compareFast(a: string, b: string): Promise<boolean> {
const rand = await this.randomBytes(32);
const bytes = new Uint32Array(rand);

View File

@@ -1,67 +0,0 @@
import { mock } from "jest-mock-extended";
import { makeStaticByteArray } from "../../../../spec";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import {
DECRYPT_COMMAND,
DecryptCommandData,
SET_CONFIG_COMMAND,
buildDecryptMessage,
buildSetConfigMessage,
} from "./worker-command.type";
describe("Worker command types", () => {
describe("buildDecryptMessage", () => {
it("builds a message with the correct command", () => {
const commandData = createDecryptCommandData();
const result = buildDecryptMessage(commandData);
const parsedResult = JSON.parse(result);
expect(parsedResult.command).toBe(DECRYPT_COMMAND);
});
it("includes the provided data in the message", () => {
const mockItems = [{ encrypted: "test-encrypted" } as unknown as Decryptable<any>];
const commandData = createDecryptCommandData(mockItems);
const result = buildDecryptMessage(commandData);
const parsedResult = JSON.parse(result);
expect(parsedResult.command).toBe(DECRYPT_COMMAND);
expect(parsedResult.id).toBe("test-id");
expect(parsedResult.items).toEqual(mockItems);
expect(SymmetricCryptoKey.fromJSON(parsedResult.key)).toEqual(commandData.key);
});
});
describe("buildSetConfigMessage", () => {
it("builds a message with the correct command", () => {
const result = buildSetConfigMessage({ newConfig: mock<ServerConfig>() });
const parsedResult = JSON.parse(result);
expect(parsedResult.command).toBe(SET_CONFIG_COMMAND);
});
it("includes the provided data in the message", () => {
const serverConfig = { version: "test-version" } as unknown as ServerConfig;
const result = buildSetConfigMessage({ newConfig: serverConfig });
const parsedResult = JSON.parse(result);
expect(parsedResult.command).toBe(SET_CONFIG_COMMAND);
expect(ServerConfig.fromJSON(parsedResult.newConfig).version).toEqual(serverConfig.version);
});
});
});
function createDecryptCommandData(items?: Decryptable<any>[]): DecryptCommandData {
return {
id: "test-id",
items: items ?? [],
key: new SymmetricCryptoKey(makeStaticByteArray(64)),
};
}

View File

@@ -1,36 +0,0 @@
import { Jsonify } from "type-fest";
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
import { Decryptable } from "../../../platform/interfaces/decryptable.interface";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
export const DECRYPT_COMMAND = "decrypt";
export const SET_CONFIG_COMMAND = "updateConfig";
export type DecryptCommandData = {
id: string;
items: Decryptable<any>[];
key: SymmetricCryptoKey;
};
export type ParsedDecryptCommandData = {
id: string;
items: Jsonify<Decryptable<any>>[];
key: Jsonify<SymmetricCryptoKey>;
};
type SetConfigCommandData = { newConfig: ServerConfig };
export function buildDecryptMessage(data: DecryptCommandData): string {
return JSON.stringify({
command: DECRYPT_COMMAND,
...data,
});
}
export function buildSetConfigMessage(data: SetConfigCommandData): string {
return JSON.stringify({
command: SET_CONFIG_COMMAND,
...data,
});
}

View File

@@ -70,7 +70,10 @@ describe("Folder", () => {
beforeEach(() => {
encryptService = mock<EncryptService>();
encryptService.decryptString.mockResolvedValue("encName");
// Platform code is not migrated yet
encryptService.decryptString.mockImplementation((_value, _key) => {
return Promise.resolve("encName");
});
});
it("decrypts the name", async () => {

View File

@@ -14,7 +14,6 @@ import { makeStaticByteArray, makeSymmetricCryptoKey } from "../../../spec/utils
import { ApiService } from "../../abstractions/api.service";
import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service";
import { DomainSettingsService } from "../../autofill/services/domain-settings.service";
import { BulkEncryptService } from "../../key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../key-management/crypto/models/enc-string";
import { UriMatchStrategy } from "../../models/domain/domain-service";
@@ -102,7 +101,6 @@ describe("Cipher Service", () => {
const i18nService = mock<I18nService>();
const searchService = mock<SearchService>();
const encryptService = mock<EncryptService>();
const bulkEncryptService = mock<BulkEncryptService>();
const configService = mock<ConfigService>();
accountService = mockAccountServiceWith(mockUserId);
const logService = mock<LogService>();
@@ -130,7 +128,6 @@ describe("Cipher Service", () => {
stateService,
autofillSettingsService,
encryptService,
bulkEncryptService,
cipherFileUploadService,
configService,
stateProvider,
@@ -578,7 +575,6 @@ describe("Cipher Service", () => {
.calledWith(FeatureFlag.PM19941MigrateCipherDomainToSdk)
.mockResolvedValue(false);
cipherService.getKeyForCipherKeyDecryption = jest.fn().mockResolvedValue(mockUserKey);
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32));
jest
.spyOn(encryptionContext.cipher, "decrypt")
.mockResolvedValue(new CipherView(encryptionContext.cipher));

View File

@@ -13,7 +13,6 @@ import { AccountService } from "../../auth/abstractions/account.service";
import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service";
import { DomainSettingsService } from "../../autofill/services/domain-settings.service";
import { FeatureFlag } from "../../enums/feature-flag.enum";
import { BulkEncryptService } from "../../key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../key-management/crypto/models/enc-string";
import { UriMatchStrategySetting } from "../../models/domain/domain-service";
@@ -104,7 +103,6 @@ export class CipherService implements CipherServiceAbstraction {
private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private encryptService: EncryptService,
private bulkEncryptService: BulkEncryptService,
private cipherFileUploadService: CipherFileUploadService,
private configService: ConfigService,
private stateProvider: StateProvider,
@@ -506,17 +504,12 @@ export class CipherService implements CipherServiceAbstraction {
const allCipherViews = (
await Promise.all(
Object.entries(grouped).map(async ([orgId, groupedCiphers]) => {
if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) {
return await this.bulkEncryptService.decryptItems(
groupedCiphers,
keys.orgKeys?.[orgId as OrganizationId] ?? keys.userKey,
);
} else {
return await this.encryptService.decryptItems(
groupedCiphers,
keys.orgKeys?.[orgId as OrganizationId] ?? keys.userKey,
);
}
const key = keys.orgKeys[orgId as OrganizationId] ?? keys.userKey;
return await Promise.all(
groupedCiphers.map(async (cipher) => {
return await cipher.decrypt(key);
}),
);
}),
)
)
@@ -684,12 +677,11 @@ export class CipherService implements CipherServiceAbstraction {
const ciphers = response.data.map((cr) => new Cipher(new CipherData(cr)));
const key = await this.keyService.getOrgKey(organizationId);
let decCiphers: CipherView[] = [];
if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) {
decCiphers = await this.bulkEncryptService.decryptItems(ciphers, key);
} else {
decCiphers = await this.encryptService.decryptItems(ciphers, key);
}
const decCiphers: CipherView[] = await Promise.all(
ciphers.map(async (cipher) => {
return await cipher.decrypt(key);
}),
);
decCiphers.sort(this.getLocaleSortingFunction());
return decCiphers;

View File

@@ -49,7 +49,6 @@ describe("Folder Service", () => {
keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any));
encryptService.decryptString.mockResolvedValue("DEC");
encryptService.decryptToUtf8.mockResolvedValue("DEC");
folderService = new FolderService(
keyService,

View File

@@ -697,7 +697,6 @@ describe("keyService", () => {
});
it("returns decryption keys when some of the org keys are providers", async () => {
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64));
const org2Id = "org2Id" as OrganizationId;
updateKeys({
userKey: makeSymmetricCryptoKey<UserKey>(64),

View File

@@ -54,7 +54,6 @@ function setupUserKeyValidation(
encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64));
encryptService.decryptString.mockResolvedValue("mockDecryptedString");
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
}
@@ -276,7 +275,6 @@ describe("regenerateIfNeeded", () => {
};
setupVerificationResponse(mockVerificationResponse, sdkService);
setupUserKeyValidation(cipherService, keyService, encryptService);
encryptService.decryptToBytes.mockRejectedValue(new Error("error"));
encryptService.decryptString.mockRejectedValue(new Error("error"));
encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("error"));
@@ -327,7 +325,6 @@ describe("regenerateIfNeeded", () => {
};
setupVerificationResponse(mockVerificationResponse, sdkService);
setupUserKeyValidation(cipherService, keyService, encryptService);
encryptService.decryptToBytes.mockRejectedValue(new Error("error"));
encryptService.decryptString.mockRejectedValue(new Error("error"));
encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("error"));