diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 09708859ac8..fa776285ead 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -39,6 +39,7 @@ export enum FeatureFlag { PrivateKeyRegeneration = "pm-12241-private-key-regeneration", UserKeyRotationV2 = "userkey-rotation-v2", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", + PM17987_BlockType0 = "pm-17987-block-type-0", /* Tools */ ItemShare = "item-share", @@ -121,6 +122,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.UserKeyRotationV2]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, + [FeatureFlag.PM17987_BlockType0]: FALSE, /* Platform */ [FeatureFlag.IpcChannelFramework]: FALSE, 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 d10061a2be8..10d29198ada 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 @@ -19,10 +19,17 @@ import { SymmetricCryptoKey, } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { + DefaultFeatureFlagValue, + FeatureFlag, + getFeatureFlagValue, +} from "../../../enums/feature-flag.enum"; import { ServerConfig } from "../../../platform/abstractions/config/server-config"; import { EncryptService } from "../abstractions/encrypt.service"; export class EncryptServiceImplementation implements EncryptService { + private blockType0: boolean = DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0]; + constructor( protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService, @@ -31,7 +38,7 @@ export class EncryptServiceImplementation implements EncryptService { // Handle updating private properties to turn on/off feature flags. onServerConfigChange(newConfig: ServerConfig): void { - return; + this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0); } async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise { @@ -39,6 +46,12 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No encryption key provided."); } + if (this.blockType0) { + if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) { + throw new Error("Type 0 encryption is not supported."); + } + } + if (plainValue == null) { return Promise.resolve(null); } @@ -70,6 +83,12 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No encryption key provided."); } + if (this.blockType0) { + if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) { + throw new Error("Type 0 encryption is not supported."); + } + } + const innerKey = key.inner(); if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { const encValue = await this.aesEncrypt(plainValue, innerKey); 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 275fd266f84..c65c78d88d7 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 @@ -10,6 +10,8 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { CsprngArray } from "@bitwarden/common/types/csprng"; import { makeStaticByteArray } from "../../../../spec"; +import { DefaultFeatureFlagValue, FeatureFlag } from "../../../enums/feature-flag.enum"; +import { ServerConfig } from "../../../platform/abstractions/config/server-config"; import { EncryptServiceImplementation } from "./encrypt.service.implementation"; @@ -26,17 +28,65 @@ describe("EncryptService", () => { encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); }); + describe("onServerConfigChange", () => { + const newConfig = mock(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("updates internal flag with default value when not present in config", () => { + encryptService.onServerConfigChange(newConfig); + + expect((encryptService as any).blockType0).toBe( + DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0], + ); + }); + + test.each([true, false])("updates internal flag with value in config", (expectedValue) => { + newConfig.featureStates = { [FeatureFlag.PM17987_BlockType0]: expectedValue }; + + encryptService.onServerConfigChange(newConfig); + + expect((encryptService as any).blockType0).toBe(expectedValue); + }); + }); + describe("encrypt", () => { it("throws if no key is provided", () => { return expect(encryptService.encrypt(null, null)).rejects.toThrow( "No encryption key provided.", ); }); - it("returns null if no data is provided", async () => { - const key = mock(); + + it("throws if type 0 key is provided with flag turned on", async () => { + (encryptService as any).blockType0 = true; + const key = new SymmetricCryptoKey(makeStaticByteArray(32)); + const mock32Key = mock(); + mock32Key.key = makeStaticByteArray(32); + + await expect(encryptService.encrypt(null!, key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + await expect(encryptService.encrypt(null!, mock32Key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + + const plainValue = "data"; + await expect(encryptService.encrypt(plainValue, key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + await expect(encryptService.encrypt(plainValue, mock32Key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + }); + + it("returns null if no data is provided with valid key", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const actual = await encryptService.encrypt(null, key); expect(actual).toBeNull(); }); + it("creates an EncString for Aes256Cbc", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const plainValue = "data"; @@ -53,6 +103,7 @@ describe("EncryptService", () => { expect(Utils.fromB64ToArray(result.data).length).toEqual(4); expect(Utils.fromB64ToArray(result.iv).length).toEqual(16); }); + it("creates an EncString for Aes256Cbc_HmacSha256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const plainValue = "data"; @@ -90,6 +141,21 @@ describe("EncryptService", () => { ); }); + it("throws if type 0 key provided with flag turned on", async () => { + (encryptService as any).blockType0 = true; + const key = new SymmetricCryptoKey(makeStaticByteArray(32)); + const mock32Key = mock(); + mock32Key.key = makeStaticByteArray(32); + + await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + + await expect(encryptService.encryptToBytes(plainValue, mock32Key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + }); + it("encrypts data with provided Aes256Cbc key and returns correct encbuffer", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); const iv = makeStaticByteArray(16, 80);