1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 06:23:38 +00:00

updated to use sdk function without prociding the key

This commit is contained in:
gbubemismith
2025-04-16 16:22:09 -04:00
parent c4c2f9a915
commit 55a701befd
36 changed files with 118 additions and 170 deletions

View File

@@ -152,7 +152,7 @@ describe("FidoAuthenticatorService", () => {
id === excludedCipher.id ? ({ decrypt: () => excludedCipher } as any) : undefined,
);
cipherService.getAllDecrypted.mockResolvedValue([excludedCipher]);
cipherService.decryptCipherWithSdkOrLegacy.mockResolvedValue(excludedCipher);
cipherService.decrypt.mockResolvedValue(excludedCipher);
});
/**
@@ -221,7 +221,7 @@ describe("FidoAuthenticatorService", () => {
id === existingCipher.id ? ({ decrypt: () => existingCipher } as any) : undefined,
);
cipherService.getAllDecrypted.mockResolvedValue([existingCipher]);
cipherService.decryptCipherWithSdkOrLegacy.mockResolvedValue(existingCipher);
cipherService.decrypt.mockResolvedValue(existingCipher);
});
/**
@@ -308,7 +308,7 @@ describe("FidoAuthenticatorService", () => {
const encryptedCipher = { ...existingCipher, reprompt: CipherRepromptType.Password };
cipherService.get.mockResolvedValue(encryptedCipher as unknown as Cipher);
cipherService.decryptCipherWithSdkOrLegacy.mockResolvedValue({
cipherService.decrypt.mockResolvedValue({
...existingCipher,
reprompt: CipherRepromptType.Password,
} as unknown as CipherView);
@@ -354,7 +354,7 @@ describe("FidoAuthenticatorService", () => {
cipherId === cipher.id ? ({ decrypt: () => cipher } as any) : undefined,
);
cipherService.getAllDecrypted.mockResolvedValue([await cipher]);
cipherService.decryptCipherWithSdkOrLegacy.mockResolvedValue(cipher);
cipherService.decrypt.mockResolvedValue(cipher);
cipherService.encrypt.mockImplementation(async (cipher) => {
cipher.login.fido2Credentials[0].credentialId = credentialId; // Replace id for testability
return {} as any;

View File

@@ -151,7 +151,7 @@ export class Fido2AuthenticatorService<ParentWindowReference>
);
const encrypted = await this.cipherService.get(cipherId, activeUserId);
cipher = await this.cipherService.decryptCipherWithSdkOrLegacy(encrypted, activeUserId);
cipher = await this.cipherService.decrypt(encrypted, activeUserId);
if (
!userVerified &&

View File

@@ -221,5 +221,5 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
* @param userId The user ID to use for decryption.
* @returns A promise that resolves to the decrypted cipher view.
*/
abstract decryptCipherWithSdkOrLegacy(cipher: Cipher, userId: UserId): Promise<CipherView>;
abstract decrypt(cipher: Cipher, userId: UserId): Promise<CipherView>;
}

View File

@@ -477,12 +477,12 @@ describe("Cipher Service", () => {
});
});
describe("decryptCipherWithSdkOrLegacy", () => {
describe("decrypt", () => {
it("should call decrypt method of CipherEncryptionService when feature flag is true", async () => {
configService.getFeatureFlag.mockResolvedValue(true);
cipherEncryptionService.decrypt.mockResolvedValue(new CipherView(cipherObj));
const result = await cipherService.decryptCipherWithSdkOrLegacy(cipherObj, userId);
const result = await cipherService.decrypt(cipherObj, userId);
expect(result).toEqual(new CipherView(cipherObj));
expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(cipherObj, userId);
@@ -495,7 +495,7 @@ describe("Cipher Service", () => {
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32));
jest.spyOn(cipherObj, "decrypt").mockResolvedValue(new CipherView(cipherObj));
const result = await cipherService.decryptCipherWithSdkOrLegacy(cipherObj, userId);
const result = await cipherService.decrypt(cipherObj, userId);
expect(result).toEqual(new CipherView(cipherObj));
expect(cipherObj.decrypt).toHaveBeenCalledWith(mockUserKey);

View File

@@ -428,57 +428,57 @@ export class CipherService implements CipherServiceAbstraction {
): Promise<[CipherView[], CipherView[]] | null> {
if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) {
return this.decryptCiphersWithSdk(ciphers, userId);
} else {
const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true));
if (keys == null || (keys.userKey == null && Object.keys(keys.orgKeys).length === 0)) {
// return early if there are no keys to decrypt with
return null;
}
// Group ciphers by orgId or under 'null' for the user's ciphers
const grouped = ciphers.reduce(
(agg, c) => {
agg[c.organizationId] ??= [];
agg[c.organizationId].push(c);
return agg;
},
{} as Record<string, Cipher[]>,
);
const decryptStartTime = new Date().getTime();
const allCipherViews = (
await Promise.all(
Object.entries(grouped).map(async ([orgId, groupedCiphers]) => {
if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) {
return await this.bulkEncryptService.decryptItems(
groupedCiphers,
keys.orgKeys[orgId as OrganizationId] ?? keys.userKey,
);
} else {
return await this.encryptService.decryptItems(
groupedCiphers,
keys.orgKeys[orgId as OrganizationId] ?? keys.userKey,
);
}
}),
)
)
.flat()
.sort(this.getLocaleSortingFunction());
this.logService.info(
`[CipherService] Decrypting ${allCipherViews.length} ciphers took ${new Date().getTime() - decryptStartTime}ms`,
);
// Split ciphers into two arrays, one for successfully decrypted ciphers and one for ciphers that failed to decrypt
return allCipherViews.reduce(
(acc, c) => {
if (c.decryptionFailure) {
acc[1].push(c);
} else {
acc[0].push(c);
}
return acc;
},
[[], []] as [CipherView[], CipherView[]],
);
}
const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true));
if (keys == null || (keys.userKey == null && Object.keys(keys.orgKeys).length === 0)) {
// return early if there are no keys to decrypt with
return null;
}
// Group ciphers by orgId or under 'null' for the user's ciphers
const grouped = ciphers.reduce(
(agg, c) => {
agg[c.organizationId] ??= [];
agg[c.organizationId].push(c);
return agg;
},
{} as Record<string, Cipher[]>,
);
const decryptStartTime = new Date().getTime();
const allCipherViews = (
await Promise.all(
Object.entries(grouped).map(async ([orgId, groupedCiphers]) => {
if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) {
return await this.bulkEncryptService.decryptItems(
groupedCiphers,
keys.orgKeys[orgId as OrganizationId] ?? keys.userKey,
);
} else {
return await this.encryptService.decryptItems(
groupedCiphers,
keys.orgKeys[orgId as OrganizationId] ?? keys.userKey,
);
}
}),
)
)
.flat()
.sort(this.getLocaleSortingFunction());
this.logService.info(
`[CipherService] Decrypting ${allCipherViews.length} ciphers took ${new Date().getTime() - decryptStartTime}ms`,
);
// Split ciphers into two arrays, one for successfully decrypted ciphers and one for ciphers that failed to decrypt
return allCipherViews.reduce(
(acc, c) => {
if (c.decryptionFailure) {
acc[1].push(c);
} else {
acc[0].push(c);
}
return acc;
},
[[], []] as [CipherView[], CipherView[]],
);
}
/**
@@ -487,7 +487,7 @@ export class CipherService implements CipherServiceAbstraction {
* @param userId The user ID to use for decryption.
* @returns A promise that resolves to the decrypted cipher view.
*/
async decryptCipherWithSdkOrLegacy(cipher: Cipher, userId: UserId): Promise<CipherView> {
async decrypt(cipher: Cipher, userId: UserId): Promise<CipherView> {
if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) {
return await this.cipherEncryptionService.decrypt(cipher, userId);
} else {
@@ -907,7 +907,7 @@ export class CipherService implements CipherServiceAbstraction {
//then we rollback to using the user key as the main key of encryption of the item
//in order to keep item and it's attachments with the same encryption level
if (cipher.key != null && !cipherKeyEncryptionEnabled) {
const model = await this.decryptCipherWithSdkOrLegacy(cipher, userId);
const model = await this.decrypt(cipher, userId);
cipher = await this.encrypt(model, userId);
await this.updateWithServer(cipher);
}
@@ -1438,7 +1438,7 @@ export class CipherService implements CipherServiceAbstraction {
originalCipher: Cipher,
userId: UserId,
): Promise<void> {
const existingCipher = await this.decryptCipherWithSdkOrLegacy(originalCipher, userId);
const existingCipher = await this.decrypt(originalCipher, userId);
model.passwordHistory = existingCipher.passwordHistory || [];
if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) {
if (

View File

@@ -212,7 +212,6 @@ describe("DefaultCipherEncryptionService", () => {
);
expect(mockSdkClient.vault().ciphers().decrypt_fido2_private_key).toHaveBeenCalledWith(
sdkCipherView,
fido2Credentials[0].keyValue,
);
expect(Fido2CredentialView.fromSdkFido2CredentialView).toHaveBeenCalledTimes(1);
});

View File

@@ -43,15 +43,14 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
.ciphers()
.decrypt_fido2_credentials(sdkCipherView);
// TEMPORARY: Manually decrypt the keyValue for Fido2 credentials since 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) => {
// TEMPORARY: Manually decrypt the keyValue for Fido2 credentials since don't currently use
// the SDK for Fido2 Authentication.
const decryptedKeyValue = ref.value
.vault()
.ciphers()
.decrypt_fido2_private_key(sdkCipherView, f.keyValue);
const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!;
return {