1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-22 12:24:01 +00:00

Created cipher encryption abstraction and service

This commit is contained in:
gbubemismith
2025-04-02 18:02:00 -04:00
parent 1def6398d2
commit 0e146f4142
2 changed files with 115 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
import { CipherListView } from "@bitwarden/sdk-internal";
import { UserId } from "../../types/guid";
import { Cipher } from "../models/domain/cipher";
import { CipherView } from "../models/view/cipher.view";
/**
* Service responsible for encrypting and decrypting ciphers.
*/
export abstract class CipherEncryptionService {
/**
* Decrypts a cipher using the SDK for the given userId.
*
* @param userId The user ID whose key will be used for decryption
* @param cipher The encrypted cipher object
*
* @returns A promise that resolves to the decrypted cipher view
*/
abstract decrypt(userId: UserId, cipher: Cipher): Promise<CipherView>;
/**
* Decrypts a list of ciphers using the SDK for the given userId.
*
* @param userId The user ID whose key will be used for decryption
* @param ciphers The encrypted cipher objects
*
* @returns A promise that resolves to an array of decrypted cipher list views
*/
abstract decryptCipherList(userId: UserId, ciphers: Cipher[]): Promise<CipherListView[]>;
}

View File

@@ -0,0 +1,86 @@
import { EMPTY, catchError, firstValueFrom, map } from "rxjs";
import { CipherListView } from "@bitwarden/sdk-internal";
import { LogService } from "../../platform/abstractions/log.service";
import { SdkService } from "../../platform/abstractions/sdk/sdk.service";
import { UserId } from "../../types/guid";
import { CipherEncryptionService } from "../abstractions/cipher-encryption.service";
import { CipherType } from "../enums";
import { Cipher } from "../models/domain/cipher";
import { CipherView } from "../models/view/cipher.view";
import { Fido2CredentialView } from "../models/view/fido2-credential.view";
export class DefaultCipherEncryptionService implements CipherEncryptionService {
constructor(
private sdkService: SdkService,
private logService: LogService,
) {}
/**
* {@inheritdoc}
*/
async decrypt(userId: UserId, cipher: Cipher): Promise<CipherView> {
return firstValueFrom(
this.sdkService.userClient$(userId).pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
using ref = sdk.take();
const sdkCipherView = ref.value.vault().ciphers().decrypt(cipher.toSdkCipher());
// The SDK returns a cipherView or throws an error if decryption fails.
const clientCipherView = CipherView.fromSdkCipherView(sdkCipherView)!;
// Decrypt Fido2 credentials if available
if (
clientCipherView.type === CipherType.Login &&
sdkCipherView.login?.fido2Credentials?.length
) {
const fido2CredentialViews = ref.value
.vault()
.ciphers()
.decrypt_fido2_credentials(sdkCipherView);
clientCipherView.login.fido2Credentials = fido2CredentialViews
.map((f) => Fido2CredentialView.fromSdkFido2CredentialView(f))
.filter((view): view is Fido2CredentialView => view !== undefined);
}
return clientCipherView;
}),
catchError((error: unknown) => {
this.logService.error(`Failed to decrypt cipher ${cipher.id}: ${error}`);
return EMPTY;
}),
),
);
}
/**
* {@inheritdoc}
*/
async decryptCipherList(userId: UserId, ciphers: Cipher[]): Promise<CipherListView[]> {
return firstValueFrom(
this.sdkService.userClient$(userId).pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK is undefined");
}
using ref = sdk.take();
return ref.value
.vault()
.ciphers()
.decrypt_list(ciphers.map((cipher) => cipher.toSdkCipher()));
}),
catchError((error: unknown) => {
this.logService.error(`Failed to decrypt cipher list: ${error}`);
return EMPTY;
}),
),
);
}
}