diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 707fa7b670..7ba55a4589 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1483,6 +1483,7 @@ export default class MainBackground { await this.sdkLoadService.loadAndInit(); // Only the "true" background should run migrations await this.migrationRunner.run(); + this.encryptService.init(this.configService); // This is here instead of in the InitService b/c we don't plan for // side effects to run in the Browser InitService. diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 1930dbd1d4..1de1731013 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -3,6 +3,8 @@ 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 { 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"; @@ -29,6 +31,8 @@ export class InitService { private sdkLoadService: SdkLoadService, private viewCacheService: PopupViewCacheService, private readonly migrationRunner: MigrationRunner, + private configService: ConfigService, + private encryptService: EncryptService, @Inject(DOCUMENT) private document: Document, ) {} @@ -40,6 +44,7 @@ export class InitService { this.twoFactorService.init(); await this.viewCacheService.init(); await this.sizeService.init(); + this.encryptService.init(this.configService); const htmlEl = window.document.documentElement; this.themingService.applyThemeChangesTo(this.document); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 3453e0cff7..d10849bae0 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -984,6 +984,7 @@ export class ServiceContainer { this.containerService.attachToGlobal(global); await this.i18nService.init(); this.twoFactorService.init(); + this.encryptService.init(this.configService); // If a user has a BW_SESSION key stored in their env (not process.env.BW_SESSION), // this should set the user key to unlock the vault on init. diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 6b511ff366..79c93c1390 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.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"; @@ -52,6 +53,7 @@ export class InitService { private autofillService: DesktopAutofillService, private autotypeService: DesktopAutotypeService, private sdkLoadService: SdkLoadService, + private configService: ConfigService, @Inject(DOCUMENT) private document: Document, private readonly migrationRunner: MigrationRunner, ) {} @@ -62,6 +64,7 @@ export class InitService { await this.sshAgentService.init(); this.nativeMessagingService.init(); await this.migrationRunner.waitForCompletion(); // Desktop will run migrations in the main process + this.encryptService.init(this.configService); const accounts = await firstValueFrom(this.accountService.accounts$); const setUserKeyInMemoryPromises = []; diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index a3358ff725..6b03913ef7 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.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 { IpcService } from "@bitwarden/common/platform/ipc"; @@ -40,6 +41,7 @@ export class InitService { private ipcService: IpcService, private sdkLoadService: SdkLoadService, private taskService: TaskService, + private configService: ConfigService, private readonly migrationRunner: MigrationRunner, @Inject(DOCUMENT) private document: Document, ) {} @@ -48,6 +50,7 @@ export class InitService { return async () => { await this.sdkLoadService.loadAndInit(); await this.migrationRunner.run(); + this.encryptService.init(this.configService); const activeAccount = await firstValueFrom(this.accountService.activeAccount$); if (activeAccount) { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index f6090c01d2..0ad63b630f 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -34,6 +34,7 @@ export enum FeatureFlag { PrivateKeyRegeneration = "pm-12241-private-key-regeneration", EnrollAeadOnKeyRotation = "enroll-aead-on-key-rotation", ForceUpdateKDFSettings = "pm-18021-force-update-kdf-settings", + PM25174_DisableType0Decryption = "pm-25174-disable-type-0-decryption", UnlockWithMasterPasswordUnlockData = "pm-23246-unlock-with-master-password-unlock-data", /* Tools */ @@ -113,6 +114,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.EnrollAeadOnKeyRotation]: FALSE, [FeatureFlag.ForceUpdateKDFSettings]: FALSE, + [FeatureFlag.PM25174_DisableType0Decryption]: FALSE, [FeatureFlag.UnlockWithMasterPasswordUnlockData]: FALSE, /* Platform */ diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index 87af385211..25e5f949b4 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -1,8 +1,16 @@ +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + 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 { + /** + * A temporary init method to make the encrypt service listen to feature-flag changes. + * This will be removed once the feature flag has been rolled out. + */ + abstract init(configService: ConfigService): void; + /** * Encrypts a string to an EncString * @param plainValue - The value to encrypt diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index 6daede6be6..132bbc306c 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -1,7 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { 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 { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; @@ -13,12 +15,28 @@ import { PureCrypto } from "@bitwarden/sdk-internal"; import { EncryptService } from "../abstractions/encrypt.service"; export class EncryptServiceImplementation implements EncryptService { + private disableType0Decryption = false; + constructor( protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService, protected logMacFailures: boolean, ) {} + init(configService: ConfigService): void { + configService.serverConfig$.subscribe((newConfig) => { + if (newConfig != null) { + this.setDisableType0Decryption( + newConfig.featureStates[FeatureFlag.PM25174_DisableType0Decryption] === true, + ); + } + }); + } + + setDisableType0Decryption(disable: boolean): void { + this.disableType0Decryption = disable; + } + async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise { if (plainValue == null) { this.logService.warning( @@ -42,16 +60,25 @@ export class EncryptServiceImplementation implements EncryptService { } async decryptString(encString: EncString, key: SymmetricCryptoKey): Promise { + if (this.disableType0Decryption && encString.encryptionType === EncryptionType.AesCbc256_B64) { + throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); + } await SdkLoadService.Ready; return PureCrypto.symmetric_decrypt_string(encString.encryptedString, key.toEncoded()); } async decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise { + if (this.disableType0Decryption && encString.encryptionType === EncryptionType.AesCbc256_B64) { + throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); + } await SdkLoadService.Ready; return PureCrypto.symmetric_decrypt_bytes(encString.encryptedString, key.toEncoded()); } async decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise { + if (this.disableType0Decryption && encBuffer.encryptionType === EncryptionType.AesCbc256_B64) { + throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); + } await SdkLoadService.Ready; return PureCrypto.symmetric_decrypt_filedata(encBuffer.buffer, key.toEncoded()); } @@ -121,6 +148,13 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No wrappingKey provided for unwrapping."); } + if ( + this.disableType0Decryption && + wrappedDecapsulationKey.encryptionType === EncryptionType.AesCbc256_B64 + ) { + throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); + } + await SdkLoadService.Ready; return PureCrypto.unwrap_decapsulation_key( wrappedDecapsulationKey.encryptedString, @@ -137,6 +171,12 @@ export class EncryptServiceImplementation implements EncryptService { if (wrappingKey == null) { throw new Error("No wrappingKey provided for unwrapping."); } + if ( + this.disableType0Decryption && + wrappedEncapsulationKey.encryptionType === EncryptionType.AesCbc256_B64 + ) { + throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); + } await SdkLoadService.Ready; return PureCrypto.unwrap_encapsulation_key( @@ -154,6 +194,12 @@ export class EncryptServiceImplementation implements EncryptService { if (wrappingKey == null) { throw new Error("No wrappingKey provided for unwrapping."); } + if ( + this.disableType0Decryption && + keyToBeUnwrapped.encryptionType === EncryptionType.AesCbc256_B64 + ) { + throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); + } await SdkLoadService.Ready; return new SymmetricCryptoKey( diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts index 0cc7824a91..466f59da7c 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -171,6 +171,15 @@ describe("EncryptService", () => { key.toEncoded(), ); }); + + it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { + encryptService.setDisableType0Decryption(true); + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "encrypted_string"); + await expect(encryptService.decryptString(encString, key)).rejects.toThrow( + "Decryption of AesCbc256_B64 encrypted data is disabled.", + ); + }); }); describe("decryptBytes", () => { @@ -184,6 +193,15 @@ describe("EncryptService", () => { key.toEncoded(), ); }); + + it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { + encryptService.setDisableType0Decryption(true); + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "encrypted_bytes"); + await expect(encryptService.decryptBytes(encString, key)).rejects.toThrow( + "Decryption of AesCbc256_B64 encrypted data is disabled.", + ); + }); }); describe("decryptFileData", () => { @@ -197,6 +215,20 @@ describe("EncryptService", () => { key.toEncoded(), ); }); + + it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { + encryptService.setDisableType0Decryption(true); + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encBuffer = EncArrayBuffer.fromParts( + EncryptionType.AesCbc256_B64, + new Uint8Array(16), + new Uint8Array(32), + null, + ); + await expect(encryptService.decryptFileData(encBuffer, key)).rejects.toThrow( + "Decryption of AesCbc256_B64 encrypted data is disabled.", + ); + }); }); describe("unwrapDecapsulationKey", () => { @@ -210,6 +242,14 @@ describe("EncryptService", () => { key.toEncoded(), ); }); + it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { + encryptService.setDisableType0Decryption(true); + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_decapsulation_key"); + await expect(encryptService.unwrapDecapsulationKey(encString, key)).rejects.toThrow( + "Decryption of AesCbc256_B64 encrypted data is disabled.", + ); + }); it("throws if wrappedDecapsulationKey is null", () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); return expect(encryptService.unwrapDecapsulationKey(null, key)).rejects.toThrow( @@ -235,6 +275,14 @@ describe("EncryptService", () => { key.toEncoded(), ); }); + it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { + encryptService.setDisableType0Decryption(true); + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_encapsulation_key"); + await expect(encryptService.unwrapEncapsulationKey(encString, key)).rejects.toThrow( + "Decryption of AesCbc256_B64 encrypted data is disabled.", + ); + }); it("throws if wrappedEncapsulationKey is null", () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); return expect(encryptService.unwrapEncapsulationKey(null, key)).rejects.toThrow( @@ -260,6 +308,14 @@ describe("EncryptService", () => { key.toEncoded(), ); }); + it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { + encryptService.setDisableType0Decryption(true); + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_symmetric_key"); + await expect(encryptService.unwrapSymmetricKey(encString, key)).rejects.toThrow( + "Decryption of AesCbc256_B64 encrypted data is disabled.", + ); + }); it("throws if keyToBeUnwrapped is null", () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); return expect(encryptService.unwrapSymmetricKey(null, key)).rejects.toThrow(