mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[PM-17440] Use SDK for decryption (#14277)
* Improve dev logging * Define decrypt with sdk flag * Use SDK's pure crypto functions for decryption feature flagged by `use-sdk-for-decryption` * Avoid pushing decryption requests to web workers for SDK web workers are able to use the SDK, but they require the SDK module to be initialized. If this is eventually seen as desired, we'll need client-specific worker scripts. * Apply suggestions from code review Co-authored-by: Bernd Schoolmann <mail@quexten.com> * fixup! Apply suggestions from code review * fixup: Update feature flag state in config callbacks * Apply suggestions from code review Co-authored-by: Bernd Schoolmann <mail@quexten.com> --------- Co-authored-by: Bernd Schoolmann <mail@quexten.com>
This commit is contained in:
@@ -45,6 +45,7 @@ export enum FeatureFlag {
|
||||
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
||||
UserKeyRotationV2 = "userkey-rotation-v2",
|
||||
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
|
||||
UseSDKForDecryption = "use-sdk-for-decryption",
|
||||
PM17987_BlockType0 = "pm-17987-block-type-0",
|
||||
|
||||
/* Tools */
|
||||
@@ -130,6 +131,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
||||
[FeatureFlag.UserKeyRotationV2]: FALSE,
|
||||
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
|
||||
[FeatureFlag.UseSDKForDecryption]: FALSE,
|
||||
[FeatureFlag.PM17987_BlockType0]: FALSE,
|
||||
|
||||
/* Platform */
|
||||
|
||||
@@ -12,6 +12,11 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer";
|
||||
|
||||
import {
|
||||
DefaultFeatureFlagValue,
|
||||
FeatureFlag,
|
||||
getFeatureFlagValue,
|
||||
} from "../../../enums/feature-flag.enum";
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { buildDecryptMessage, buildSetConfigMessage } from "../types/worker-command.type";
|
||||
|
||||
@@ -24,6 +29,7 @@ export class BulkEncryptServiceImplementation implements BulkEncryptService {
|
||||
private workers: Worker[] = [];
|
||||
private timeout: any;
|
||||
private currentServerConfig: ServerConfig | undefined = undefined;
|
||||
protected useSDKForDecryption: boolean = DefaultFeatureFlagValue[FeatureFlag.UseSDKForDecryption];
|
||||
|
||||
private clear$ = new Subject<void>();
|
||||
|
||||
@@ -48,7 +54,7 @@ export class BulkEncryptServiceImplementation implements BulkEncryptService {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
if (typeof window === "undefined" || this.useSDKForDecryption) {
|
||||
this.logService.info("Window not available in BulkEncryptService, decrypting sequentially");
|
||||
const results = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
@@ -63,6 +69,7 @@ export class BulkEncryptServiceImplementation implements BulkEncryptService {
|
||||
|
||||
onServerConfigChange(newConfig: ServerConfig): void {
|
||||
this.currentServerConfig = newConfig;
|
||||
this.useSDKForDecryption = getFeatureFlagValue(newConfig, FeatureFlag.UseSDKForDecryption);
|
||||
this.updateWorkerServerConfigs(newConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Aes256CbcKey,
|
||||
SymmetricCryptoKey,
|
||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { PureCrypto } from "@bitwarden/sdk-internal";
|
||||
|
||||
import {
|
||||
DefaultFeatureFlagValue,
|
||||
@@ -28,6 +29,7 @@ import { ServerConfig } from "../../../platform/abstractions/config/server-confi
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
|
||||
export class EncryptServiceImplementation implements EncryptService {
|
||||
protected useSDKForDecryption: boolean = DefaultFeatureFlagValue[FeatureFlag.UseSDKForDecryption];
|
||||
private blockType0: boolean = DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0];
|
||||
|
||||
constructor(
|
||||
@@ -132,6 +134,13 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
|
||||
// Handle updating private properties to turn on/off feature flags.
|
||||
onServerConfigChange(newConfig: ServerConfig): void {
|
||||
const oldFlagValue = this.useSDKForDecryption;
|
||||
this.useSDKForDecryption = getFeatureFlagValue(newConfig, FeatureFlag.UseSDKForDecryption);
|
||||
this.logService.debug(
|
||||
"[EncryptService] Updated sdk decryption flag",
|
||||
oldFlagValue,
|
||||
this.useSDKForDecryption,
|
||||
);
|
||||
this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0);
|
||||
}
|
||||
|
||||
@@ -228,6 +237,15 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
key: SymmetricCryptoKey,
|
||||
decryptContext: string = "no context",
|
||||
): Promise<string> {
|
||||
if (this.useSDKForDecryption) {
|
||||
this.logService.debug("decrypting with SDK");
|
||||
if (encString == null || encString.encryptedString == null) {
|
||||
throw new Error("encString is null or undefined");
|
||||
}
|
||||
return PureCrypto.symmetric_decrypt(encString.encryptedString, key.toEncoded());
|
||||
}
|
||||
this.logService.debug("decrypting with javascript");
|
||||
|
||||
if (key == null) {
|
||||
throw new Error("No key provided for decryption.");
|
||||
}
|
||||
@@ -291,6 +309,25 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
key: SymmetricCryptoKey,
|
||||
decryptContext: string = "no context",
|
||||
): Promise<Uint8Array | null> {
|
||||
if (this.useSDKForDecryption) {
|
||||
this.logService.debug("[EncryptService] Decrypting bytes with SDK");
|
||||
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;
|
||||
return PureCrypto.symmetric_decrypt_array_buffer(buffer, key.toEncoded());
|
||||
}
|
||||
this.logService.debug("[EncryptService] Decrypting bytes with javascript");
|
||||
|
||||
if (key == null) {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ export class MultithreadEncryptServiceImplementation extends EncryptServiceImple
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.useSDKForDecryption) {
|
||||
return await super.decryptItems(items, key);
|
||||
}
|
||||
|
||||
this.logService.info("Starting decryption using multithreading");
|
||||
|
||||
if (this.worker == null) {
|
||||
|
||||
@@ -57,6 +57,41 @@ export class EncArrayBuffer implements Encrypted {
|
||||
);
|
||||
}
|
||||
|
||||
static fromParts(
|
||||
encryptionType: EncryptionType,
|
||||
iv: Uint8Array,
|
||||
data: Uint8Array,
|
||||
mac: Uint8Array | undefined | null,
|
||||
) {
|
||||
if (encryptionType == null || iv == null || data == null) {
|
||||
throw new Error("encryptionType, iv, and data must be provided");
|
||||
}
|
||||
|
||||
switch (encryptionType) {
|
||||
case EncryptionType.AesCbc256_B64:
|
||||
case EncryptionType.AesCbc256_HmacSha256_B64:
|
||||
EncArrayBuffer.validateIvLength(iv);
|
||||
EncArrayBuffer.validateMacLength(encryptionType, mac);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown EncryptionType ${encryptionType} for EncArrayBuffer.fromParts`);
|
||||
}
|
||||
|
||||
let macLen = 0;
|
||||
if (mac != null) {
|
||||
macLen = mac.length;
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(1 + iv.byteLength + macLen + data.byteLength);
|
||||
bytes.set([encryptionType], 0);
|
||||
bytes.set(iv, 1);
|
||||
if (mac != null) {
|
||||
bytes.set(mac, 1 + iv.byteLength);
|
||||
}
|
||||
bytes.set(data, 1 + iv.byteLength + macLen);
|
||||
return new EncArrayBuffer(bytes);
|
||||
}
|
||||
|
||||
static async fromResponse(response: {
|
||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||
}): Promise<EncArrayBuffer> {
|
||||
@@ -71,4 +106,27 @@ export class EncArrayBuffer implements Encrypted {
|
||||
const buffer = Utils.fromB64ToArray(b64);
|
||||
return new EncArrayBuffer(buffer);
|
||||
}
|
||||
|
||||
static validateIvLength(iv: Uint8Array) {
|
||||
if (iv == null || iv.length !== IV_LENGTH) {
|
||||
throw new Error("Invalid IV length");
|
||||
}
|
||||
}
|
||||
|
||||
static validateMacLength(encType: EncryptionType, mac: Uint8Array | null | undefined) {
|
||||
switch (encType) {
|
||||
case EncryptionType.AesCbc256_B64:
|
||||
if (mac != null) {
|
||||
throw new Error("mac must not be provided for AesCbc256_B64");
|
||||
}
|
||||
break;
|
||||
case EncryptionType.AesCbc256_HmacSha256_B64:
|
||||
if (mac == null || mac.length !== MAC_LENGTH) {
|
||||
throw new Error("Invalid MAC length");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid encryption type and mac combination");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user