mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 13:40:06 +00:00
Add cose
This commit is contained in:
@@ -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!");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user