1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 13:40:06 +00:00
This commit is contained in:
Bernd Schoolmann
2025-03-06 15:06:46 +01:00
parent 756bc67607
commit 4aeb20239f
10 changed files with 109 additions and 21 deletions

View File

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

View File

@@ -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,

View File

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

View File

@@ -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,

View File

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

View File

@@ -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,

View File

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

View File

@@ -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];

View File

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

View File

@@ -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<Record<CipherId, LocalData>> {
@@ -443,6 +445,8 @@ export class CipherService implements CipherServiceAbstraction {
{} as Record<string, Cipher[]>,
);
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(