From 013a34e0429eb3183bd2d334fc6f63e4fba5706c Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 5 May 2025 09:19:52 -0700 Subject: [PATCH] [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 * fixup! Apply suggestions from code review * fixup: Update feature flag state in config callbacks * Apply suggestions from code review Co-authored-by: Bernd Schoolmann --------- Co-authored-by: Bernd Schoolmann --- .../src/services/jslib-services.module.ts | 6 +- libs/common/src/enums/feature-flag.enum.ts | 2 + .../bulk-encrypt.service.implementation.ts | 9 ++- .../encrypt.service.implementation.ts | 37 ++++++++++++ ...tithread-encrypt.service.implementation.ts | 4 ++ .../models/domain/enc-array-buffer.ts | 58 +++++++++++++++++++ 6 files changed, 114 insertions(+), 2 deletions(-) diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 0d59f4a6547..5596e66b4d6 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -586,7 +586,11 @@ const safeProviders: SafeProvider[] = [ useClass: AvatarService, deps: [ApiServiceAbstraction, StateProvider], }), - safeProvider({ provide: LogService, useFactory: () => new ConsoleLogService(false), deps: [] }), + safeProvider({ + provide: LogService, + useFactory: () => new ConsoleLogService(process.env.NODE_ENV === "development"), + deps: [], + }), safeProvider({ provide: CollectionService, useClass: DefaultCollectionService, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 3e905a6253c..7174923f129 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -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 */ diff --git a/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts index c22944ba217..7421ae1eb20 100644 --- a/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts @@ -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(); @@ -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); } 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 fb0649f7c5b..5bb946b25bf 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 @@ -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 { + 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 { + 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."); } diff --git a/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts index 589450b23cc..a3a663f72a9 100644 --- a/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts @@ -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) { diff --git a/libs/common/src/platform/models/domain/enc-array-buffer.ts b/libs/common/src/platform/models/domain/enc-array-buffer.ts index 8b69cb347ba..55ecb3dfb16 100644 --- a/libs/common/src/platform/models/domain/enc-array-buffer.ts +++ b/libs/common/src/platform/models/domain/enc-array-buffer.ts @@ -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; }): Promise { @@ -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"); + } + } }