mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-17987] Add feature flag (#13991)
* Add feature flag * Add unit tests.
This commit is contained in:
@@ -39,6 +39,7 @@ export enum FeatureFlag {
|
|||||||
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
||||||
UserKeyRotationV2 = "userkey-rotation-v2",
|
UserKeyRotationV2 = "userkey-rotation-v2",
|
||||||
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
|
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
|
||||||
|
PM17987_BlockType0 = "pm-17987-block-type-0",
|
||||||
|
|
||||||
/* Tools */
|
/* Tools */
|
||||||
ItemShare = "item-share",
|
ItemShare = "item-share",
|
||||||
@@ -121,6 +122,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
||||||
[FeatureFlag.UserKeyRotationV2]: FALSE,
|
[FeatureFlag.UserKeyRotationV2]: FALSE,
|
||||||
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
|
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
|
||||||
|
[FeatureFlag.PM17987_BlockType0]: FALSE,
|
||||||
|
|
||||||
/* Platform */
|
/* Platform */
|
||||||
[FeatureFlag.IpcChannelFramework]: FALSE,
|
[FeatureFlag.IpcChannelFramework]: FALSE,
|
||||||
|
|||||||
@@ -19,10 +19,17 @@ import {
|
|||||||
SymmetricCryptoKey,
|
SymmetricCryptoKey,
|
||||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
} 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 { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||||
import { EncryptService } from "../abstractions/encrypt.service";
|
import { EncryptService } from "../abstractions/encrypt.service";
|
||||||
|
|
||||||
export class EncryptServiceImplementation implements EncryptService {
|
export class EncryptServiceImplementation implements EncryptService {
|
||||||
|
private blockType0: boolean = DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cryptoFunctionService: CryptoFunctionService,
|
protected cryptoFunctionService: CryptoFunctionService,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
@@ -31,7 +38,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
|||||||
|
|
||||||
// Handle updating private properties to turn on/off feature flags.
|
// Handle updating private properties to turn on/off feature flags.
|
||||||
onServerConfigChange(newConfig: ServerConfig): void {
|
onServerConfigChange(newConfig: ServerConfig): void {
|
||||||
return;
|
this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
|
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
|
||||||
@@ -39,6 +46,12 @@ export class EncryptServiceImplementation implements EncryptService {
|
|||||||
throw new Error("No encryption key provided.");
|
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) {
|
if (plainValue == null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
@@ -70,6 +83,12 @@ export class EncryptServiceImplementation implements EncryptService {
|
|||||||
throw new Error("No encryption key provided.");
|
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();
|
const innerKey = key.inner();
|
||||||
if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
|
if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
|
||||||
const encValue = await this.aesEncrypt(plainValue, innerKey);
|
const encValue = await this.aesEncrypt(plainValue, innerKey);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
|||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
|
|
||||||
import { makeStaticByteArray } from "../../../../spec";
|
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";
|
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
|
||||||
|
|
||||||
@@ -26,17 +28,65 @@ describe("EncryptService", () => {
|
|||||||
encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true);
|
encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("onServerConfigChange", () => {
|
||||||
|
const newConfig = mock<ServerConfig>();
|
||||||
|
|
||||||
|
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", () => {
|
describe("encrypt", () => {
|
||||||
it("throws if no key is provided", () => {
|
it("throws if no key is provided", () => {
|
||||||
return expect(encryptService.encrypt(null, null)).rejects.toThrow(
|
return expect(encryptService.encrypt(null, null)).rejects.toThrow(
|
||||||
"No encryption key provided.",
|
"No encryption key provided.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("returns null if no data is provided", async () => {
|
|
||||||
const key = mock<SymmetricCryptoKey>();
|
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<SymmetricCryptoKey>();
|
||||||
|
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);
|
const actual = await encryptService.encrypt(null, key);
|
||||||
expect(actual).toBeNull();
|
expect(actual).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates an EncString for Aes256Cbc", async () => {
|
it("creates an EncString for Aes256Cbc", async () => {
|
||||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||||
const plainValue = "data";
|
const plainValue = "data";
|
||||||
@@ -53,6 +103,7 @@ describe("EncryptService", () => {
|
|||||||
expect(Utils.fromB64ToArray(result.data).length).toEqual(4);
|
expect(Utils.fromB64ToArray(result.data).length).toEqual(4);
|
||||||
expect(Utils.fromB64ToArray(result.iv).length).toEqual(16);
|
expect(Utils.fromB64ToArray(result.iv).length).toEqual(16);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates an EncString for Aes256Cbc_HmacSha256_B64", async () => {
|
it("creates an EncString for Aes256Cbc_HmacSha256_B64", async () => {
|
||||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||||
const plainValue = "data";
|
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<SymmetricCryptoKey>();
|
||||||
|
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 () => {
|
it("encrypts data with provided Aes256Cbc key and returns correct encbuffer", async () => {
|
||||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0));
|
const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0));
|
||||||
const iv = makeStaticByteArray(16, 80);
|
const iv = makeStaticByteArray(16, 80);
|
||||||
|
|||||||
Reference in New Issue
Block a user