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