diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 72744d9f0e1..d7ee258dd84 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -12,7 +12,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; @@ -20,7 +21,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; +import { Argon2KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; +import { Kdf, PureCrypto } from "@bitwarden/sdk-internal"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth/core"; @@ -96,8 +98,29 @@ export class UserKeyRotationService { const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig); - const [newUnencryptedUserKey, newMasterKeyEncryptedUserKey] = - await this.keyService.makeUserKey(newMasterKey); + const userkey = PureCrypto.generate_userkey(false); + const newUnencryptedUserKey = new SymmetricCryptoKey(userkey) as UserKey; + let kdf: Kdf = { pBKDF2: { iterations: 1 } }; + if (kdfConfig.kdfType === KdfType.PBKDF2_SHA256) { + kdf.pBKDF2.iterations = kdfConfig.iterations; + } else { + const argon2Config = kdfConfig as Argon2KdfConfig; + kdf = { + argon2id: { + iterations: argon2Config.iterations, + memory: argon2Config.memory, + parallelism: argon2Config.parallelism, + }, + }; + } + const encryptedUserkey = PureCrypto.encrypt_userkey_with_masterpassword( + userkey, + newMasterPassword, + user.email, + kdf, + ); + const newMasterKeyEncryptedUserKey = new EncString(encryptedUserkey); + this.logService.info("[Userkey rotation] User key created", userkey, encryptedUserkey); if (!newUnencryptedUserKey || !newMasterKeyEncryptedUserKey) { this.logService.info("[Userkey rotation] User key could not be created. Aborting!"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index dc6c049101a..d5833dd0479 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -151,8 +151,8 @@ import { OrganizationBillingService } from "@bitwarden/common/billing/services/o import { TaxService } from "@bitwarden/common/billing/services/tax.service"; import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation"; -import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation"; +import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; +import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service"; import { DefaultVaultTimeoutService, DefaultVaultTimeoutSettingsService, @@ -907,13 +907,13 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: EncryptService, - useClass: MultithreadEncryptServiceImplementation, + useClass: EncryptServiceImplementation, deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES], }), safeProvider({ provide: BulkEncryptService, - useClass: BulkEncryptServiceImplementation, - deps: [CryptoFunctionServiceAbstraction, LogService], + useClass: FallbackBulkEncryptService, + deps: [EncryptService], }), safeProvider({ provide: EventUploadServiceAbstraction, diff --git a/libs/common/src/auth/services/key-connector.service.ts b/libs/common/src/auth/services/key-connector.service.ts index 081459556db..0548f81f646 100644 --- a/libs/common/src/auth/services/key-connector.service.ts +++ b/libs/common/src/auth/services/key-connector.service.ts @@ -20,7 +20,10 @@ import { KeysRequest } from "../../models/request/keys.request"; import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; import { LogService } from "../../platform/abstractions/log.service"; import { Utils } from "../../platform/misc/utils"; -import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { + Aes256CbcKey, + SymmetricCryptoKey, +} from "../../platform/models/domain/symmetric-crypto-key"; import { ActiveUserState, KEY_CONNECTOR_DISK, @@ -96,7 +99,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { const organization = await this.getManagingOrganization(userId); const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); const keyConnectorRequest = new KeyConnectorUserKeyRequest( - Utils.fromBufferToB64(masterKey.inner().encryptionKey), + Utils.fromBufferToB64((masterKey.inner() as Aes256CbcKey).encryptionKey), ); try { @@ -160,7 +163,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { kdfConfig, ); const keyConnectorRequest = new KeyConnectorUserKeyRequest( - Utils.fromBufferToB64(masterKey.inner().encryptionKey), + Utils.fromBufferToB64((masterKey.inner() as Aes256CbcKey).encryptionKey), ); await this.masterPasswordService.setMasterKey(masterKey, userId); diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 16680cc711a..1cf884b5fff 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -86,7 +86,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, /* Key Management */ - [FeatureFlag.UseSDKForDecryption]: FALSE, + [FeatureFlag.UseSDKForDecryption]: true, /* Tools */ [FeatureFlag.ItemShare]: FALSE, @@ -100,7 +100,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, [FeatureFlag.SSHKeyVaultItem]: FALSE, [FeatureFlag.SSHAgent]: FALSE, - [FeatureFlag.UserKeyRotationV2]: FALSE, + [FeatureFlag.UserKeyRotationV2]: true, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, 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 11ca60a1970..4732c919335 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 @@ -66,6 +66,12 @@ export class EncryptServiceImplementation implements EncryptService { const data = Utils.fromBufferToB64(encObj.data); const mac = Utils.fromBufferToB64(encObj.mac); return new EncString(innerKey.type, data, iv, mac); + } else if (innerKey.type === EncryptionType.XChaCha20Poly1305_B64) { + const encrypted = PureCrypto.symmetric_encrypt( + Utils.fromBufferToByteString(plainBuf), + Utils.fromBufferToB64(innerKey.coseKey), + ); + return new EncString(encrypted); } else { throw new Error(`Encrypt is not supported for keys of type ${innerKey.type}`); } @@ -95,6 +101,12 @@ export class EncryptServiceImplementation implements EncryptService { encBytes.set(new Uint8Array(encValue.iv), 1); encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength); return new EncArrayBuffer(encBytes); + } else if (innerKey.type === EncryptionType.XChaCha20Poly1305_B64) { + const encrypted = PureCrypto.symmetric_decrypt_array_buffer( + plainValue, + Utils.fromBufferToB64(innerKey.coseKey), + ); + return new EncArrayBuffer(encrypted); } } @@ -108,7 +120,12 @@ export class EncryptServiceImplementation implements EncryptService { if (encString == null || encString.encryptedString == null) { throw new Error("encString is null or undefined"); } - return PureCrypto.symmetric_decrypt(encString.encryptedString, key.keyB64); + try { + return PureCrypto.symmetric_decrypt(encString.encryptedString, key.keyB64); + } catch (e) { + this.logService.error("Error decrypting with SDK", e); + return null; + } } this.logService.debug("decrypting with javascript"); @@ -175,18 +192,27 @@ export class EncryptServiceImplementation implements EncryptService { mode: "cbc", parameters: fastParams, }); + } else if (innerKey.type === EncryptionType.XChaCha20Poly1305_B64) { + return PureCrypto.symmetric_decrypt(encString.encryptedString, key.keyB64); } else { throw new Error(`Unsupported encryption type`); } } async decryptToBytes( - encThing: Encrypted, + encThing: Encrypted | EncString, key: SymmetricCryptoKey, decryptContext: string = "no context", ): Promise { if (this.useSDKForDecryption) { this.logService.debug("decrypting bytes with SDK"); + if (encThing.encryptionType === EncryptionType.XChaCha20Poly1305_B64) { + const buffer = new Uint8Array(encThing.dataBytes.length + 1); + buffer[0] = encThing.encryptionType; + buffer.set(encThing.dataBytes, 1); + return PureCrypto.symmetric_decrypt_array_buffer(buffer, key.keyB64); + } + if ( encThing.encryptionType == null || encThing.ivBytes == null || @@ -200,6 +226,7 @@ export class EncryptServiceImplementation implements EncryptService { encThing.dataBytes, encThing.macBytes, ).buffer; + return PureCrypto.symmetric_decrypt_array_buffer(buffer, key.keyB64); } this.logService.debug("decrypting bytes with javascript"); @@ -269,6 +296,14 @@ export class EncryptServiceImplementation implements EncryptService { inner.encryptionKey, "cbc", ); + } else if (inner.type === EncryptionType.XChaCha20Poly1305_B64) { + const buffer = EncArrayBuffer.fromParts( + encThing.encryptionType, + encThing.ivBytes, + encThing.dataBytes, + encThing.macBytes, + ).buffer; + return PureCrypto.symmetric_decrypt_array_buffer(buffer, key.keyB64); } } diff --git a/libs/common/src/platform/enums/encryption-type.enum.ts b/libs/common/src/platform/enums/encryption-type.enum.ts index bf616958f07..8ee51241c11 100644 --- a/libs/common/src/platform/enums/encryption-type.enum.ts +++ b/libs/common/src/platform/enums/encryption-type.enum.ts @@ -2,6 +2,8 @@ export enum EncryptionType { AesCbc256_B64 = 0, // Type 1 was the unused and removed AesCbc128_HmacSha256_B64 AesCbc256_HmacSha256_B64 = 2, + XChaCha20Poly1305_B64 = 7, + Rsa2048_OaepSha256_B64 = 3, Rsa2048_OaepSha1_B64 = 4, Rsa2048_OaepSha256_HmacSha256_B64 = 5, @@ -10,8 +12,8 @@ export enum EncryptionType { export const SymmetricEncryptionTypes = [ EncryptionType.AesCbc256_B64, - EncryptionType.AesCbc128_HmacSha256_B64, EncryptionType.AesCbc256_HmacSha256_B64, + EncryptionType.XChaCha20Poly1305_B64, ] as const; export const AsymmetricEncryptionTypes = [ @@ -45,6 +47,7 @@ export function encryptionTypeToString(encryptionType: EncryptionType): string { export const EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE = { [EncryptionType.AesCbc256_B64]: 2, [EncryptionType.AesCbc256_HmacSha256_B64]: 3, + [EncryptionType.XChaCha20Poly1305_B64]: 1, [EncryptionType.Rsa2048_OaepSha256_B64]: 1, [EncryptionType.Rsa2048_OaepSha1_B64]: 1, [EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64]: 2, 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 7e8643d3176..ff3d72e154c 100644 --- a/libs/common/src/platform/models/domain/enc-array-buffer.ts +++ b/libs/common/src/platform/models/domain/enc-array-buffer.ts @@ -64,7 +64,6 @@ export class EncArrayBuffer implements Encrypted { switch (encryptionType) { case EncryptionType.AesCbc256_B64: - case EncryptionType.AesCbc128_HmacSha256_B64: case EncryptionType.AesCbc256_HmacSha256_B64: EncArrayBuffer.validateIvLength(iv); EncArrayBuffer.validateMacLength(encryptionType, mac); @@ -116,7 +115,6 @@ export class EncArrayBuffer implements Encrypted { throw new Error("mac must not be provided for AesCbc256_B64"); } break; - case EncryptionType.AesCbc128_HmacSha256_B64: case EncryptionType.AesCbc256_HmacSha256_B64: if (mac == null || mac.length !== MAC_LENGTH) { throw new Error("Invalid MAC length"); diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index 1dc6cce1e94..8f7eda3bed2 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -101,6 +101,9 @@ export class EncString implements Encrypted { this.iv = encPieces[0]; this.data = encPieces[1]; break; + case EncryptionType.XChaCha20Poly1305_B64: + this.data = encPieces[0]; + break; case EncryptionType.Rsa2048_OaepSha256_B64: case EncryptionType.Rsa2048_OaepSha1_B64: this.data = encPieces[0]; diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index 010483eefd2..098478d5ab6 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -16,13 +16,19 @@ export type Aes256CbcKey = { encryptionKey: Uint8Array; }; +export type XChaCha20Poly1305_B64 = { + type: EncryptionType.XChaCha20Poly1305_B64; + // todo inner key for no-parse + coseKey: Uint8Array; +}; + /** * A symmetric crypto key represents a symmetric key usable for symmetric encryption and decryption operations. * The specific algorithm used is private to the key, and should only be exposed to encrypt service implementations. * This can be done via `inner()`. */ export class SymmetricCryptoKey { - private innerKey: Aes256CbcHmacKey | Aes256CbcKey; + private innerKey: Aes256CbcHmacKey | Aes256CbcKey | XChaCha20Poly1305_B64; key: Uint8Array; keyB64: string; @@ -50,6 +56,13 @@ export class SymmetricCryptoKey { }; this.key = key; this.keyB64 = this.toBase64(); + } else if (key.byteLength > 64) { + this.innerKey = { + type: EncryptionType.XChaCha20Poly1305_B64, + coseKey: key, + }; + this.key = key; + this.keyB64 = this.toBase64(); } else { throw new Error(`Unsupported encType/key length ${key.byteLength}`); } @@ -63,7 +76,7 @@ export class SymmetricCryptoKey { /** * @returns The inner key instance that can be directly used for encryption primitives */ - inner(): Aes256CbcHmacKey | Aes256CbcKey { + inner(): Aes256CbcHmacKey | Aes256CbcKey | XChaCha20Poly1305_B64 { return this.innerKey; } @@ -90,6 +103,9 @@ export class SymmetricCryptoKey { encodedKey.set(this.innerKey.encryptionKey, 0); encodedKey.set(this.innerKey.authenticationKey, 32); return encodedKey; + } else if (this.innerKey.type === EncryptionType.XChaCha20Poly1305_B64) { + const encodedKey = this.key; + return encodedKey; } else { throw new Error("Unsupported encryption type."); } diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 05c875e7546..aa6378685c3 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -14,6 +14,7 @@ import { } from "rxjs"; import { SemVer } from "semver"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; @@ -111,6 +112,7 @@ export class CipherService implements CipherServiceAbstraction { private configService: ConfigService, private stateProvider: StateProvider, private accountService: AccountService, + private logService: LogService, ) {} localData$(userId: UserId): Observable> { @@ -443,6 +445,8 @@ export class CipherService implements CipherServiceAbstraction { {} as Record, ); + this.logService.info(`[CipherService] starting decrypt of ${ciphers.length} ciphers`); + const time = Date.now(); const allCipherViews = ( await Promise.all( Object.entries(grouped).map(async ([orgId, groupedCiphers]) => { @@ -462,6 +466,9 @@ export class CipherService implements CipherServiceAbstraction { ) .flat() .sort(this.getLocaleSortingFunction()); + this.logService.info( + `[CipherService] finished decrypt of ${ciphers.length} ciphers in ${Date.now() - time}ms`, + ); // Split ciphers into two arrays, one for successfully decrypted ciphers and one for ciphers that failed to decrypt return allCipherViews.reduce(