1
0
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:
Matt Gibson
2025-05-05 09:19:52 -07:00
committed by GitHub
parent e0cabd1df0
commit 013a34e042
6 changed files with 114 additions and 2 deletions

View File

@@ -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 */

View File

@@ -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);
}

View File

@@ -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.");
}

View File

@@ -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) {

View File

@@ -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");
}
}
}