1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-22136] Implement SDK cipher encryption (#15337)

* [PM-22136] Update sdk cipher view map to support uknown uuid type

* [PM-22136] Add key to CipherView for copying to SdkCipherView for encryption

* [PM-22136] Add fromSdk* helpers to Cipher domain objects

* [PM-22136] Add toSdk* helpers to Cipher View objects

* [PM-22136] Add encrypt() to cipher encryption service

* [PM-22136] Add feature flag

* [PM-22136] Use new SDK encrypt method when feature flag is enabled

* [PM-22136] Filter out null/empty URIs

* [PM-22136] Change default value for cipher view arrays to []. See ADR-0014.

* [PM-22136] Keep encrypted key value on attachment so that it is passed to the SDK

* [PM-22136] Keep encrypted key value on CipherView so that it is passed to the SDK during encryption

* [PM-22136] Update failing attachment test

* [PM-22136] Update failing importer tests due to new default value for arrays

* [PM-22136] Update CipherView.fromJson to handle the prototype of EncString for the cipher key

* [PM-22136] Add tickets for followup work

* [PM-22136] Use new set_fido2_credentials SDK method instead

* [PM-22136] Fix missing prototype when decrypting Fido2Credentials

* [PM-22136] Fix test after sdk change

* [PM-22136] Update @bitwarden/sdk-internal version

* [PM-22136] Fix some strict typing errors

* [PM-23348] Migrate move cipher to org to SDK (#15567)

* [PM-23348] Add moveToOrganization method to cipher-encryption.service.ts

* [PM-23348] Use cipherEncryptionService.moveToOrganization in cipherService shareWithServer and shareManyWithServer methods

* [PM-23348] Update cipherFormService to use the shareWithServer() method instead of encrypt()

* [PM-23348] Fix typo

* [PM-23348] Add missing docs

* [PM-22136] Fix EncString import after merge with main
This commit is contained in:
Shane Melton
2025-07-21 23:27:01 -07:00
committed by GitHub
parent 81ee26733e
commit 391f540d1f
43 changed files with 1485 additions and 149 deletions

View File

@@ -1,10 +1,15 @@
import { EMPTY, catchError, firstValueFrom, map } from "rxjs";
import { CipherListView } from "@bitwarden/sdk-internal";
import { EncryptionContext } from "@bitwarden/common/vault/abstractions/cipher.service";
import {
CipherListView,
BitwardenClient,
CipherView as SdkCipherView,
} 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 { SdkService, asUuid } from "../../platform/abstractions/sdk/sdk.service";
import { UserId, OrganizationId } from "../../types/guid";
import { CipherEncryptionService } from "../abstractions/cipher-encryption.service";
import { CipherType } from "../enums";
import { Cipher } from "../models/domain/cipher";
@@ -18,6 +23,67 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
private logService: LogService,
) {}
async encrypt(model: CipherView, userId: UserId): Promise<EncryptionContext | undefined> {
return firstValueFrom(
this.sdkService.userClient$(userId).pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
using ref = sdk.take();
const sdkCipherView = this.toSdkCipherView(model, ref.value);
const encryptionContext = ref.value.vault().ciphers().encrypt(sdkCipherView);
return {
cipher: Cipher.fromSdkCipher(encryptionContext.cipher)!,
encryptedFor: asUuid<UserId>(encryptionContext.encryptedFor),
};
}),
catchError((error: unknown) => {
this.logService.error(`Failed to encrypt cipher: ${error}`);
return EMPTY;
}),
),
);
}
async moveToOrganization(
model: CipherView,
organizationId: OrganizationId,
userId: UserId,
): Promise<EncryptionContext | undefined> {
return firstValueFrom(
this.sdkService.userClient$(userId).pipe(
map((sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
using ref = sdk.take();
const sdkCipherView = this.toSdkCipherView(model, ref.value);
const movedCipherView = ref.value
.vault()
.ciphers()
.move_to_organization(sdkCipherView, asUuid(organizationId));
const encryptionContext = ref.value.vault().ciphers().encrypt(movedCipherView);
return {
cipher: Cipher.fromSdkCipher(encryptionContext.cipher)!,
encryptedFor: asUuid<UserId>(encryptionContext.encryptedFor),
};
}),
catchError((error: unknown) => {
this.logService.error(`Failed to move cipher to organization: ${error}`);
return EMPTY;
}),
),
);
}
async decrypt(cipher: Cipher, userId: UserId): Promise<CipherView> {
return firstValueFrom(
this.sdkService.userClient$(userId).pipe(
@@ -51,11 +117,8 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
clientCipherView.login.fido2Credentials = fido2CredentialViews
.map((f) => {
const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!;
return {
...view,
keyValue: decryptedKeyValue,
};
view.keyValue = decryptedKeyValue;
return view;
})
.filter((view): view is Fido2CredentialView => view !== undefined);
}
@@ -104,10 +167,8 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
clientCipherView.login.fido2Credentials = fido2CredentialViews
.map((f) => {
const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!;
return {
...view,
keyValue: decryptedKeyValue,
};
view.keyValue = decryptedKeyValue;
return view;
})
.filter((view): view is Fido2CredentialView => view !== undefined);
}
@@ -187,4 +248,25 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
),
);
}
/**
* Helper method to convert a CipherView model to an SDK CipherView. Has special handling for Fido2 credentials
* that need to be encrypted before being sent to the SDK.
* @param model The CipherView model to convert
* @param sdk An instance of SDK client
* @private
*/
private toSdkCipherView(model: CipherView, sdk: BitwardenClient): SdkCipherView {
let sdkCipherView = model.toSdkCipherView();
if (model.type === CipherType.Login && model.login?.hasFido2Credentials) {
// Encrypt Fido2 credentials separately
const fido2Credentials = model.login.fido2Credentials?.map((f) =>
f.toSdkFido2CredentialFullView(),
);
sdkCipherView = sdk.vault().ciphers().set_fido2_credentials(sdkCipherView, fido2Credentials);
}
return sdkCipherView;
}
}