1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

[PM-12423] Migrate Cipher Decryption to Use SDK (#14206)

* Created mappings for client domain object to SDK

* Add abstract decrypt observable

* Added todo for future consideration

* Added implementation to cipher service

* Added adapter and unit tests

* Created cipher encryption abstraction and service

* Register cipher encryption service

* Added tests for the cipher encryption service

* changed signature

* Updated feature flag name

* added new function to be used for decrypting ciphers

* Added new encryptedKey field

* added new function to be used for decrypting ciphers

* Manually set fields

* Added encrypted key in attachment view

* Fixed test

* Updated references to use decrypt with feature flag

* Added dependency

* updated package.json

* lint fix

* fixed tests

* Fixed small mapping issues

* Fixed test

* Added function to decrypt fido2 key value

* Added function to decrypt fido2 key value and updated test

* updated to use sdk function without prociding the key

* updated localdata sdk type change

* decrypt attachment content using sdk

* Fixed dependency issues

* updated package.json

* Refactored service to handle getting decrypted buffer using the legacy and sdk implementations

* updated services and component to use refactored version

* Updated decryptCiphersWithSdk to use decryptManyLegacy for batch decryption, ensuring the SDK is only called once per batch

* Fixed merge conflicts

* Fixed merge conflicts

* Fixed merge conflicts

* Fixed lint issues

* Moved getDecryptedAttachmentBuffer to cipher service

* Moved getDecryptedAttachmentBuffer to cipher service

* ensure CipherView properties are null instead of undefined

* Fixed test

* ensure AttachmentView properties are null instead of undefined

* Linked ticket in comment

* removed unused orgKey
This commit is contained in:
SmithThe4th
2025-05-14 10:30:01 -04:00
committed by GitHub
parent 3e0cc7ca7f
commit ad3121f535
85 changed files with 2171 additions and 218 deletions

View File

@@ -0,0 +1,190 @@
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 { AttachmentView } from "../models/view/attachment.view";
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,
) {}
async decrypt(cipher: Cipher, userId: UserId): 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());
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);
// TEMPORARY: Manually decrypt the keyValue for Fido2 credentials
// since we don't currently use the SDK for Fido2 Authentication.
const decryptedKeyValue = ref.value
.vault()
.ciphers()
.decrypt_fido2_private_key(sdkCipherView);
clientCipherView.login.fido2Credentials = fido2CredentialViews
.map((f) => {
const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!;
return {
...view,
keyValue: decryptedKeyValue,
};
})
.filter((view): view is Fido2CredentialView => view !== undefined);
}
return clientCipherView;
}),
catchError((error: unknown) => {
this.logService.error(`Failed to decrypt cipher ${error}`);
return EMPTY;
}),
),
);
}
decryptManyLegacy(ciphers: Cipher[], userId: UserId): Promise<CipherView[]> {
return firstValueFrom(
this.sdkService.userClient$(userId).pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
using ref = sdk.take();
return ciphers.map((cipher) => {
const sdkCipherView = ref.value.vault().ciphers().decrypt(cipher.toSdkCipher());
const clientCipherView = CipherView.fromSdkCipherView(sdkCipherView)!;
// Handle FIDO2 credentials if present
if (
clientCipherView.type === CipherType.Login &&
sdkCipherView.login?.fido2Credentials?.length
) {
const fido2CredentialViews = ref.value
.vault()
.ciphers()
.decrypt_fido2_credentials(sdkCipherView);
// TODO (PM-21259): Remove manual keyValue decryption for FIDO2 credentials.
// This is a temporary workaround until we can use the SDK for FIDO2 authentication.
const decryptedKeyValue = ref.value
.vault()
.ciphers()
.decrypt_fido2_private_key(sdkCipherView);
clientCipherView.login.fido2Credentials = fido2CredentialViews
.map((f) => {
const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!;
return {
...view,
keyValue: decryptedKeyValue,
};
})
.filter((view): view is Fido2CredentialView => view !== undefined);
}
return clientCipherView;
});
}),
catchError((error: unknown) => {
this.logService.error(`Failed to decrypt ciphers: ${error}`);
return EMPTY;
}),
),
);
}
async decryptMany(ciphers: Cipher[], userId: UserId): 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;
}),
),
);
}
/**
* Decrypts an attachment's content from a response object.
*
* @param cipher The encrypted cipher object that owns the attachment
* @param attachment The encrypted attachment object
* @param encryptedContent The encrypted content as a Uint8Array
* @param userId The user ID whose key will be used for decryption
*
* @returns A promise that resolves to the decrypted content
*/
async decryptAttachmentContent(
cipher: Cipher,
attachment: AttachmentView,
encryptedContent: Uint8Array,
userId: UserId,
): Promise<Uint8Array> {
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()
.attachments()
.decrypt_buffer(
cipher.toSdkCipher(),
attachment.toSdkAttachmentView(),
encryptedContent,
);
}),
catchError((error: unknown) => {
this.logService.error(`Failed to decrypt cipher buffer: ${error}`);
return EMPTY;
}),
),
);
}
}