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:
@@ -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;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user