From b09305577f450922fe0a37f3ee35e756eb603eb8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 15 Apr 2025 16:39:02 +0200 Subject: [PATCH] [PM-19603] Change asymmetric interface to only allow key encapsulation (#14046) * Change asymmetric interface to only allow key encapsulation * Fix naming * Clean up naming * Update libs/common/src/key-management/crypto/abstractions/encrypt.service.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/abstractions/encrypt.service.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Fix test --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> --- .../bulk/base-bulk-confirm.component.ts | 2 +- .../members/members.component.ts | 2 +- ...zation-user-reset-password.service.spec.ts | 9 +- ...rganization-user-reset-password.service.ts | 12 ++- .../rotateable-key-set.service.spec.ts | 2 +- .../services/rotateable-key-set.service.ts | 10 ++- .../services/emergency-access.service.spec.ts | 10 ++- .../services/emergency-access.service.ts | 14 ++-- .../accept-organization.service.spec.ts | 6 +- .../accept-organization.service.ts | 2 +- .../organization-auth-request.service.spec.ts | 13 ++- .../organization-auth-request.service.ts | 6 +- .../providers/manage/members.component.ts | 2 +- .../auth/components/set-password.component.ts | 5 +- .../default-set-password-jit.service.spec.ts | 4 +- .../default-set-password-jit.service.ts | 2 +- .../webauthn-login.strategy.spec.ts | 12 +-- .../webauthn-login.strategy.ts | 5 +- .../auth-request/auth-request.service.spec.ts | 32 ++++--- .../auth-request/auth-request.service.ts | 20 ++--- .../domain/encrypted-organization-key.ts | 6 +- ...-enrollment.service.implementation.spec.ts | 4 +- ...reset-enrollment.service.implementation.ts | 2 +- .../crypto/abstractions/encrypt.service.ts | 31 +++++++ .../encrypt.service.implementation.ts | 84 +++++++++++-------- .../crypto/services/encrypt.service.spec.ts | 37 ++++---- .../device-trust.service.implementation.ts | 10 +-- .../services/device-trust.service.spec.ts | 18 ++-- libs/key-management/src/key.service.spec.ts | 4 +- libs/key-management/src/key.service.ts | 6 +- 30 files changed, 229 insertions(+), 143 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts index 05e302f011d..6b3f958fe51 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts @@ -71,7 +71,7 @@ export abstract class BaseBulkConfirmComponent implements OnInit { if (publicKey == null) { continue; } - const encryptedKey = await this.encryptService.rsaEncrypt(key.key, publicKey); + const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(key, publicKey); userIdsWithKeys.push({ id: user.id, key: encryptedKey.encryptedString, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index a64247339a5..3ce78354046 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -322,7 +322,7 @@ export class MembersComponent extends BaseMembersComponent async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise { const orgKey = await this.keyService.getOrgKey(this.organization.id); - const key = await this.encryptService.rsaEncrypt(orgKey.key, publicKey); + const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey); const request = new OrganizationUserConfirmRequest(); request.key = key.encryptedString; await this.organizationUserApiService.postOrganizationUserConfirm( diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts index ce167950727..575c1017f21 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts @@ -89,7 +89,7 @@ describe("OrganizationUserResetPasswordService", () => { }), ); - encryptService.rsaEncrypt.mockResolvedValue( + encryptService.encapsulateKeyUnsigned.mockResolvedValue( new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"), ); }); @@ -111,7 +111,10 @@ describe("OrganizationUserResetPasswordService", () => { it("should rsa encrypt the user key", async () => { await sut.buildRecoveryKey(mockOrgId, mockUserKey, mockPublicKeys); - expect(encryptService.rsaEncrypt).toHaveBeenCalledWith(expect.anything(), expect.anything()); + expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + ); }); it("should throw an error if the public key is not trusted", async () => { @@ -199,7 +202,7 @@ describe("OrganizationUserResetPasswordService", () => { publicKey: Utils.fromUtf8ToArray("test-public-key"), }), ); - encryptService.rsaEncrypt.mockResolvedValue( + encryptService.encapsulateKeyUnsigned.mockResolvedValue( new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"), ); }); diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 4b5c03a5a5b..78d2d8fd165 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -14,7 +14,6 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { @@ -59,6 +58,10 @@ export class OrganizationUserResetPasswordService userKey: UserKey, trustedPublicKeys: Uint8Array[], ): Promise { + if (userKey == null) { + throw new Error("User key is required for recovery."); + } + // Retrieve Public Key const orgKeys = await this.organizationApiService.getKeys(orgId); if (orgKeys == null) { @@ -76,7 +79,8 @@ export class OrganizationUserResetPasswordService } // RSA Encrypt user key with organization's public key - const encryptedKey = await this.encryptService.rsaEncrypt(userKey.key, publicKey); + const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey); + return encryptedKey.encryptedString; } @@ -114,11 +118,11 @@ export class OrganizationUserResetPasswordService ); // Decrypt User's Reset Password Key to get UserKey - const decValue = await this.encryptService.rsaDecrypt( + const userKey = await this.encryptService.decapsulateKeyUnsigned( new EncString(response.resetPasswordKey), decPrivateKey, ); - const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey; + const existingUserKey = userKey as UserKey; // determine Kdf Algorithm const kdfConfig: KdfConfig = diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts index 1241ea88fe9..1a83fed37b7 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts @@ -35,7 +35,7 @@ describe("RotateableKeySetService", () => { const encryptedPrivateKey = Symbol(); keyService.makeKeyPair.mockResolvedValue(["publicKey", encryptedPrivateKey as any]); keyService.getUserKey.mockResolvedValue({ key: userKey.key } as any); - encryptService.rsaEncrypt.mockResolvedValue(encryptedUserKey as any); + encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey as any); encryptService.encrypt.mockResolvedValue(encryptedPublicKey as any); const result = await service.createKeySet(externalKey as any); diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts index 64a8bdfbfb4..8510aa1c29a 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts @@ -25,7 +25,10 @@ export class RotateableKeySetService { const userKey = await this.keyService.getUserKey(); const rawPublicKey = Utils.fromB64ToArray(publicKey); - const encryptedUserKey = await this.encryptService.rsaEncrypt(userKey.key, rawPublicKey); + const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( + userKey, + rawPublicKey, + ); const encryptedPublicKey = await this.encryptService.encrypt(rawPublicKey, userKey); return new RotateableKeySet(encryptedUserKey, encryptedPublicKey, encryptedPrivateKey); } @@ -60,7 +63,10 @@ export class RotateableKeySetService { throw new Error("failed to rotate key set: could not decrypt public key"); } const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); - const newEncryptedUserKey = await this.encryptService.rsaEncrypt(newUserKey.key, publicKey); + const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( + newUserKey, + publicKey, + ); const newRotateableKeySet = new RotateableKeySet( newEncryptedUserKey, diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts index 6ad2c4de70e..6b4b3f2ff1e 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts @@ -130,7 +130,9 @@ describe("EmergencyAccessService", () => { keyService.getUserKey.mockResolvedValueOnce(mockUserKey); - encryptService.rsaEncrypt.mockResolvedValueOnce(mockUserPublicKeyEncryptedUserKey); + encryptService.encapsulateKeyUnsigned.mockResolvedValueOnce( + mockUserPublicKeyEncryptedUserKey, + ); emergencyAccessApiService.postEmergencyAccessConfirm.mockResolvedValueOnce(); @@ -160,7 +162,9 @@ describe("EmergencyAccessService", () => { const mockDecryptedGrantorUserKey = new Uint8Array(64); keyService.getPrivateKey.mockResolvedValue(new Uint8Array(64)); - encryptService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedGrantorUserKey); + encryptService.decapsulateKeyUnsigned.mockResolvedValueOnce( + new SymmetricCryptoKey(mockDecryptedGrantorUserKey), + ); const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey; @@ -253,7 +257,7 @@ describe("EmergencyAccessService", () => { publicKey: Utils.fromUtf8ToB64("trustedPublicKey"), } as UserKeyResponse); - encryptService.rsaEncrypt.mockImplementation((plainValue, publicKey) => { + encryptService.encapsulateKeyUnsigned.mockImplementation((plainValue, publicKey) => { return Promise.resolve( new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue), ); diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index e86e0822ef3..5094c0c09ab 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -12,7 +12,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -234,11 +233,10 @@ export class EmergencyAccessService throw new Error("Active user does not have a private key, cannot get view only ciphers."); } - const grantorKeyBuffer = await this.encryptService.rsaDecrypt( + const grantorUserKey = (await this.encryptService.decapsulateKeyUnsigned( new EncString(response.keyEncrypted), activeUserPrivateKey, - ); - const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey; + )) as UserKey; let ciphers: CipherView[] = []; if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) { @@ -271,15 +269,15 @@ export class EmergencyAccessService throw new Error("Active user does not have a private key, cannot complete a takeover."); } - const grantorKeyBuffer = await this.encryptService.rsaDecrypt( + const grantorKey = await this.encryptService.decapsulateKeyUnsigned( new EncString(takeoverResponse.keyEncrypted), activeUserPrivateKey, ); - if (grantorKeyBuffer == null) { + if (grantorKey == null) { throw new Error("Failed to decrypt grantor key"); } - const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey; + const grantorUserKey = grantorKey as UserKey; let config: KdfConfig; @@ -407,6 +405,6 @@ export class EmergencyAccessService } private async encryptKey(userKey: UserKey, publicKey: Uint8Array): Promise { - return (await this.encryptService.rsaEncrypt(userKey.key, publicKey)).encryptedString; + return (await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey)).encryptedString; } } diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts index e7137e1d5bb..cc2d0e371ff 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -196,7 +196,7 @@ describe("AcceptOrganizationInviteService", () => { ); accountService.activeAccount$ = new BehaviorSubject({ id: "activeUserId" }) as any; keyService.userKey$.mockReturnValue(new BehaviorSubject({ key: "userKey" } as any)); - encryptService.rsaEncrypt.mockResolvedValue({ + encryptService.encapsulateKeyUnsigned.mockResolvedValue({ encryptedString: "encryptedString", } as EncString); @@ -218,8 +218,8 @@ describe("AcceptOrganizationInviteService", () => { expect(result).toBe(true); expect(OrganizationTrustComponent.open).toHaveBeenCalled(); - expect(encryptService.rsaEncrypt).toHaveBeenCalledWith( - "userKey", + expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( + { key: "userKey" }, Utils.fromB64ToArray("publicKey"), ); expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled(); diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts index 837031380f3..8b5db9f4872 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -202,7 +202,7 @@ export class AcceptOrganizationInviteService { const activeUserId = (await firstValueFrom(this.accountService.activeAccount$)).id; const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); // RSA Encrypt user's encKey.key with organization public key - const encryptedKey = await this.encryptService.rsaEncrypt(userKey.key, publicKey); + const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey); // Add reset password key to accept request request.resetPasswordKey = encryptedKey.encryptedString; diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts index 448399a8bb0..933a5af1760 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts @@ -7,6 +7,7 @@ import { import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; @@ -124,8 +125,10 @@ describe("OrganizationAuthRequestService", () => { ); const encryptedUserKey = new EncString("encryptedUserKey"); - encryptService.rsaDecrypt.mockResolvedValue(new Uint8Array(32)); - encryptService.rsaEncrypt.mockResolvedValue(encryptedUserKey); + encryptService.decapsulateKeyUnsigned.mockResolvedValue( + new SymmetricCryptoKey(new Uint8Array(32)), + ); + encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey); const mockPendingAuthRequest = new PendingAuthRequestView(); mockPendingAuthRequest.id = "requestId1"; @@ -166,8 +169,10 @@ describe("OrganizationAuthRequestService", () => { ); const encryptedUserKey = new EncString("encryptedUserKey"); - encryptService.rsaDecrypt.mockResolvedValue(new Uint8Array(32)); - encryptService.rsaEncrypt.mockResolvedValue(encryptedUserKey); + encryptService.decapsulateKeyUnsigned.mockResolvedValue( + new SymmetricCryptoKey(new Uint8Array(32)), + ); + encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey); const mockPendingAuthRequest = new PendingAuthRequestView(); mockPendingAuthRequest.id = "requestId1"; diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index 025b021f83d..97e271e770e 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -7,7 +7,6 @@ import { import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; @@ -119,13 +118,12 @@ export class OrganizationAuthRequestService { ); // Decrypt user key with decrypted org private key - const decValue = await this.encryptService.rsaDecrypt( + const userKey = await this.encryptService.decapsulateKeyUnsigned( new EncString(encryptedUserKey), decOrgPrivateKey, ); - const userKey = new SymmetricCryptoKey(decValue); // Re-encrypt user Key with the Device Public Key - return await this.encryptService.rsaEncrypt(userKey.key, devicePubKey); + return await this.encryptService.encapsulateKeyUnsigned(userKey, devicePubKey); } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts index 7eff2c0f0b6..4a184d2dd16 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts @@ -187,7 +187,7 @@ export class MembersComponent extends BaseMembersComponent { async confirmUser(user: ProviderUser, publicKey: Uint8Array): Promise { const providerKey = await this.keyService.getProviderKey(this.providerId); - const key = await this.encryptService.rsaEncrypt(providerKey.key, publicKey); + const key = await this.encryptService.encapsulateKeyUnsigned(providerKey, publicKey); const request = new ProviderUserConfirmRequest(); request.key = key.encryptedString; await this.apiService.postProviderUserConfirm(this.providerId, user.id, request); diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index aec0673bb52..ee0756355cf 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -211,7 +211,10 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements // RSA Encrypt user key with organization public key const userKey = await this.keyService.getUserKey(); - const encryptedUserKey = await this.encryptService.rsaEncrypt(userKey.key, publicKey); + const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( + userKey, + publicKey, + ); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); resetRequest.masterPasswordHash = masterPasswordHash; diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index cbcebd14526..12d4d8a2e39 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -174,7 +174,7 @@ describe("DefaultSetPasswordJitService", () => { } keyService.userKey$.mockReturnValue(of(userKey)); - encryptService.rsaEncrypt.mockResolvedValue(userKeyEncString); + encryptService.encapsulateKeyUnsigned.mockResolvedValue(userKeyEncString); organizationUserApiService.putOrganizationUserResetPasswordEnrollment.mockResolvedValue( undefined, @@ -216,7 +216,7 @@ describe("DefaultSetPasswordJitService", () => { // Assert expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); expect(organizationApiService.getKeys).toHaveBeenCalledWith(orgId); - expect(encryptService.rsaEncrypt).toHaveBeenCalledWith(userKey.key, orgPublicKey); + expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(userKey, orgPublicKey); expect( organizationUserApiService.putOrganizationUserResetPasswordEnrollment, ).toHaveBeenCalled(); diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index 174760aae21..2f96972d88d 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -161,7 +161,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { throw new Error("userKey not found. Could not handle reset password auto enroll."); } - const encryptedUserKey = await this.encryptService.rsaEncrypt(userKey.key, publicKey); + const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); resetRequest.masterPasswordHash = masterKeyHash; diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index aac4a36c24a..2cdeb710ab9 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -231,7 +231,9 @@ describe("WebAuthnLoginStrategy", () => { const mockUserKey = new SymmetricCryptoKey(mockUserKeyArray) as UserKey; encryptService.decryptToBytes.mockResolvedValue(mockPrfPrivateKey); - encryptService.rsaDecrypt.mockResolvedValue(mockUserKeyArray); + encryptService.decapsulateKeyUnsigned.mockResolvedValue( + new SymmetricCryptoKey(mockUserKeyArray), + ); // Act await webAuthnLoginStrategy.logIn(webAuthnCredentials); @@ -249,8 +251,8 @@ describe("WebAuthnLoginStrategy", () => { idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedPrivateKey, webAuthnCredentials.prfKey, ); - expect(encryptService.rsaDecrypt).toHaveBeenCalledTimes(1); - expect(encryptService.rsaDecrypt).toHaveBeenCalledWith( + expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledTimes(1); + expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledWith( idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedUserKey, mockPrfPrivateKey, ); @@ -278,7 +280,7 @@ describe("WebAuthnLoginStrategy", () => { // Assert expect(encryptService.decryptToBytes).not.toHaveBeenCalled(); - expect(encryptService.rsaDecrypt).not.toHaveBeenCalled(); + expect(encryptService.decapsulateKeyUnsigned).not.toHaveBeenCalled(); expect(keyService.setUserKey).not.toHaveBeenCalled(); }); @@ -330,7 +332,7 @@ describe("WebAuthnLoginStrategy", () => { apiService.postIdentityToken.mockResolvedValue(idTokenResponse); - encryptService.rsaDecrypt.mockResolvedValue(null); + encryptService.decapsulateKeyUnsigned.mockResolvedValue(null); // Act await webAuthnLoginStrategy.logIn(webAuthnCredentials); diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts index 97696a26699..895617b3237 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts @@ -7,7 +7,6 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/webauthn-login-token.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; @@ -89,13 +88,13 @@ export class WebAuthnLoginStrategy extends LoginStrategy { ); // decrypt user key with private key - const userKey = await this.encryptService.rsaDecrypt( + const userKey = await this.encryptService.decapsulateKeyUnsigned( new EncString(webAuthnPrfOption.encryptedUserKey.encryptedString), privateKey, ); if (userKey) { - await this.keyService.setUserKey(new SymmetricCryptoKey(userKey) as UserKey, userId); + await this.keyService.setUserKey(userKey as UserKey, userId); } } } diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts index 1e9c46db0ee..b5846fcfdbf 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts @@ -88,6 +88,9 @@ describe("AuthRequestService", () => { encryptService.rsaEncrypt.mockResolvedValue({ encryptedString: "ENCRYPTED_STRING", } as EncString); + encryptService.encapsulateKeyUnsigned.mockResolvedValue({ + encryptedString: "ENCRYPTED_STRING", + } as EncString); appIdService.getAppId.mockResolvedValue("APP_ID"); }); it("should throw if auth request is missing id or key", async () => { @@ -111,7 +114,10 @@ describe("AuthRequestService", () => { new AuthRequestResponse({ id: "123", publicKey: "KEY" }), ); - expect(encryptService.rsaEncrypt).toHaveBeenCalledWith(new Uint8Array(64), expect.anything()); + expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( + { encKey: new Uint8Array(64) }, + expect.anything(), + ); }); it("should use the user key if the master key and hash do not exist", async () => { @@ -122,7 +128,10 @@ describe("AuthRequestService", () => { new AuthRequestResponse({ id: "123", publicKey: "KEY" }), ); - expect(encryptService.rsaEncrypt).toHaveBeenCalledWith(new Uint8Array(64), expect.anything()); + expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( + { key: new Uint8Array(64) }, + expect.anything(), + ); }); }); describe("setUserKeyAfterDecryptingSharedUserKey", () => { @@ -214,7 +223,9 @@ describe("AuthRequestService", () => { const mockDecryptedUserKeyBytes = new Uint8Array(64); const mockDecryptedUserKey = new SymmetricCryptoKey(mockDecryptedUserKeyBytes) as UserKey; - encryptService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedUserKeyBytes); + encryptService.decapsulateKeyUnsigned.mockResolvedValueOnce( + new SymmetricCryptoKey(mockDecryptedUserKeyBytes), + ); // Act const result = await sut.decryptPubKeyEncryptedUserKey( @@ -223,7 +234,7 @@ describe("AuthRequestService", () => { ); // Assert - expect(encryptService.rsaDecrypt).toBeCalledWith( + expect(encryptService.decapsulateKeyUnsigned).toBeCalledWith( new EncString(mockPubKeyEncryptedUserKey), mockPrivateKey, ); @@ -244,9 +255,10 @@ describe("AuthRequestService", () => { const mockDecryptedMasterKeyHashBytes = new Uint8Array(64); const mockDecryptedMasterKeyHash = Utils.fromBufferToUtf8(mockDecryptedMasterKeyHashBytes); - encryptService.rsaDecrypt - .mockResolvedValueOnce(mockDecryptedMasterKeyBytes) - .mockResolvedValueOnce(mockDecryptedMasterKeyHashBytes); + encryptService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedMasterKeyHashBytes); + encryptService.decapsulateKeyUnsigned.mockResolvedValueOnce( + new SymmetricCryptoKey(mockDecryptedMasterKeyBytes), + ); // Act const result = await sut.decryptPubKeyEncryptedMasterKeyAndHash( @@ -256,13 +268,11 @@ describe("AuthRequestService", () => { ); // Assert - expect(encryptService.rsaDecrypt).toHaveBeenNthCalledWith( - 1, + expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledWith( new EncString(mockPubKeyEncryptedMasterKey), mockPrivateKey, ); - expect(encryptService.rsaDecrypt).toHaveBeenNthCalledWith( - 2, + expect(encryptService.rsaDecrypt).toHaveBeenCalledWith( new EncString(mockPubKeyEncryptedMasterKeyHash), mockPrivateKey, ); diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.ts b/libs/auth/src/common/services/auth-request/auth-request.service.ts index 1da5d2f1882..f4316c2e519 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.ts @@ -14,7 +14,6 @@ import { AuthRequestPushNotification } from "@bitwarden/common/models/response/n import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { AUTH_REQUEST_DISK_LOCAL, StateProvider, @@ -116,13 +115,12 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { Utils.fromUtf8ToArray(masterKeyHash), pubKey, ); - keyToEncrypt = masterKey.encKey; + keyToEncrypt = masterKey; } else { - const userKey = await this.keyService.getUserKey(); - keyToEncrypt = userKey.key; + keyToEncrypt = await this.keyService.getUserKey(); } - const encryptedKey = await this.encryptService.rsaEncrypt(keyToEncrypt, pubKey); + const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(keyToEncrypt, pubKey); const response = new PasswordlessAuthRequest( encryptedKey.encryptedString, @@ -171,12 +169,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { pubKeyEncryptedUserKey: string, privateKey: Uint8Array, ): Promise { - const decryptedUserKeyBytes = await this.encryptService.rsaDecrypt( + return (await this.encryptService.decapsulateKeyUnsigned( new EncString(pubKeyEncryptedUserKey), privateKey, - ); - - return new SymmetricCryptoKey(decryptedUserKeyBytes) as UserKey; + )) as UserKey; } async decryptPubKeyEncryptedMasterKeyAndHash( @@ -184,17 +180,15 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { pubKeyEncryptedMasterKeyHash: string, privateKey: Uint8Array, ): Promise<{ masterKey: MasterKey; masterKeyHash: string }> { - const decryptedMasterKeyArrayBuffer = await this.encryptService.rsaDecrypt( + const masterKey = (await this.encryptService.decapsulateKeyUnsigned( new EncString(pubKeyEncryptedMasterKey), privateKey, - ); + )) as MasterKey; const decryptedMasterKeyHashArrayBuffer = await this.encryptService.rsaDecrypt( new EncString(pubKeyEncryptedMasterKeyHash), privateKey, ); - - const masterKey = new SymmetricCryptoKey(decryptedMasterKeyArrayBuffer) as MasterKey; const masterKeyHash = Utils.fromBufferToUtf8(decryptedMasterKeyHashArrayBuffer); return { diff --git a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts index 67a5b0dd123..984d80ba519 100644 --- a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts +++ b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts @@ -31,8 +31,10 @@ export class EncryptedOrganizationKey implements BaseEncryptedOrganizationKey { constructor(private key: string) {} async decrypt(encryptService: EncryptService, privateKey: UserPrivateKey) { - const decValue = await encryptService.rsaDecrypt(this.encryptedOrganizationKey, privateKey); - return new SymmetricCryptoKey(decValue) as OrgKey; + return (await encryptService.decapsulateKeyUnsigned( + this.encryptedOrganizationKey, + privateKey, + )) as OrgKey; } get encryptedOrganizationKey() { diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index 8516400fe09..76c2d443d1d 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -100,7 +100,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { activeAccountSubject.next(Object.assign(user1AccountInfo, { id: "userId" as UserId })); keyService.getUserKey.mockResolvedValue({ key: "key" } as any); - encryptService.rsaEncrypt.mockResolvedValue(encryptedKey as any); + encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedKey as any); await service.enroll("orgId"); @@ -122,7 +122,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { }; const encryptedKey = { encryptedString: "encryptedString" }; organizationApiService.getKeys.mockResolvedValue(orgKeyResponse as any); - encryptService.rsaEncrypt.mockResolvedValue(encryptedKey as any); + encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedKey as any); await service.enroll("orgId", "userId", { key: "key" } as any); diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index 824521d8a2e..ef9c5ad3265 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -51,7 +51,7 @@ export class PasswordResetEnrollmentServiceImplementation userId ?? (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)))); userKey = userKey ?? (await this.keyService.getUserKey(userId)); // RSA Encrypt user's userKey.key with organization public key - const encryptedKey = await this.encryptService.rsaEncrypt(userKey.key, orgPublicKey); + const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(userKey, orgPublicKey); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); resetRequest.resetPasswordKey = encryptedKey.encryptedString; diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index 91e10f19069..a1e54f7064f 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -35,7 +35,38 @@ export abstract class EncryptService { key: SymmetricCryptoKey, decryptTrace?: string, ): Promise; + + /** + * Encapsulates a symmetric key with an asymmetric public key + * Note: This does not establish sender authenticity + * @param sharedKey - The symmetric key that is to be shared + * @param encapsulationKey - The encapsulation key (public key) of the receiver that the key is shared with + */ + abstract encapsulateKeyUnsigned( + sharedKey: SymmetricCryptoKey, + encapsulationKey: Uint8Array, + ): Promise; + /** + * Decapsulates a shared symmetric key with an asymmetric private key + * Note: This does not establish sender authenticity + * @param encryptedSharedKey - The encrypted shared symmetric key + * @param decapsulationKey - The key to decapsulate with (private key) + */ + abstract decapsulateKeyUnsigned( + encryptedSharedKey: EncString, + decapsulationKey: Uint8Array, + ): Promise; + /** + * @deprecated Use encapsulateKeyUnsigned instead + * @param data - The data to encrypt + * @param publicKey - The public key to encrypt with + */ abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; + /** + * @deprecated Use decapsulateKeyUnsigned instead + * @param data - The ciphertext to decrypt + * @param privateKey - The privateKey to decrypt with + */ abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; /** * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index 10d29198ada..4b299c9c6e6 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -235,42 +235,22 @@ export class EncryptServiceImplementation implements EncryptService { } } - async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise { - if (data == null) { - throw new Error("No data provided for encryption."); + async encapsulateKeyUnsigned( + sharedKey: SymmetricCryptoKey, + encapsulationKey: Uint8Array, + ): Promise { + if (sharedKey == null) { + throw new Error("No sharedKey provided for encapsulation"); } - - if (publicKey == null) { - throw new Error("No public key provided for encryption."); - } - const encrypted = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1"); - return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encrypted)); + return await this.rsaEncrypt(sharedKey.toEncoded(), encapsulationKey); } - async rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise { - if (data == null) { - throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption."); - } - - let algorithm: "sha1" | "sha256"; - switch (data.encryptionType) { - case EncryptionType.Rsa2048_OaepSha1_B64: - case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: - algorithm = "sha1"; - break; - case EncryptionType.Rsa2048_OaepSha256_B64: - case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: - algorithm = "sha256"; - break; - default: - throw new Error("Invalid encryption type."); - } - - if (privateKey == null) { - throw new Error("[Encrypt service] rsaDecrypt: No private key provided for decryption."); - } - - return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, algorithm); + async decapsulateKeyUnsigned( + encryptedSharedKey: EncString, + decapsulationKey: Uint8Array, + ): Promise { + const keyBytes = await this.rsaDecrypt(encryptedSharedKey, decapsulationKey); + return new SymmetricCryptoKey(keyBytes); } /** @@ -341,4 +321,42 @@ export class EncryptServiceImplementation implements EncryptService { this.logDecryptError(msg, keyEncType, dataEncType, decryptContext); } } + + async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise { + if (data == null) { + throw new Error("No data provided for encryption."); + } + + if (publicKey == null) { + throw new Error("No public key provided for encryption."); + } + const encrypted = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1"); + return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encrypted)); + } + + async rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise { + if (data == null) { + throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption."); + } + + let algorithm: "sha1" | "sha256"; + switch (data.encryptionType) { + case EncryptionType.Rsa2048_OaepSha1_B64: + case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: + algorithm = "sha1"; + break; + case EncryptionType.Rsa2048_OaepSha256_B64: + case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: + algorithm = "sha256"; + break; + default: + throw new Error("Invalid encryption type."); + } + + if (privateKey == null) { + throw new Error("[Encrypt service] rsaDecrypt: No private key provided for decryption."); + } + + return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, algorithm); + } } diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts index c65c78d88d7..4cbe3a3da90 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -412,7 +412,8 @@ describe("EncryptService", () => { }); describe("rsa", () => { - const data = makeStaticByteArray(10, 100); + const data = makeStaticByteArray(64, 100); + const testKey = new SymmetricCryptoKey(data); const encryptedData = makeStaticByteArray(10, 150); const publicKey = makeStaticByteArray(10, 200); const privateKey = makeStaticByteArray(10, 250); @@ -422,22 +423,26 @@ describe("EncryptService", () => { return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(data)); } - describe("rsaEncrypt", () => { + describe("encapsulateKeyUnsigned", () => { it("throws if no data is provided", () => { - return expect(encryptService.rsaEncrypt(null, publicKey)).rejects.toThrow("No data"); + return expect(encryptService.encapsulateKeyUnsigned(null, publicKey)).rejects.toThrow( + "No sharedKey provided for encapsulation", + ); }); it("throws if no public key is provided", () => { - return expect(encryptService.rsaEncrypt(data, null)).rejects.toThrow("No public key"); + return expect(encryptService.encapsulateKeyUnsigned(testKey, null)).rejects.toThrow( + "No public key", + ); }); it("encrypts data with provided key", async () => { cryptoFunctionService.rsaEncrypt.mockResolvedValue(encryptedData); - const actual = await encryptService.rsaEncrypt(data, publicKey); + const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey); expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith( - expect.toEqualBuffer(data), + expect.toEqualBuffer(testKey.key), expect.toEqualBuffer(publicKey), "sha1", ); @@ -447,13 +452,17 @@ describe("EncryptService", () => { }); }); - describe("rsaDecrypt", () => { + describe("decapsulateKeyUnsigned", () => { it("throws if no data is provided", () => { - return expect(encryptService.rsaDecrypt(null, privateKey)).rejects.toThrow("No data"); + return expect(encryptService.decapsulateKeyUnsigned(null, privateKey)).rejects.toThrow( + "No data", + ); }); it("throws if no private key is provided", () => { - return expect(encryptService.rsaDecrypt(encString, null)).rejects.toThrow("No private key"); + return expect(encryptService.decapsulateKeyUnsigned(encString, null)).rejects.toThrow( + "No private key", + ); }); it.each([EncryptionType.AesCbc256_B64, EncryptionType.AesCbc256_HmacSha256_B64])( @@ -461,16 +470,16 @@ describe("EncryptService", () => { async (encType) => { encString.encryptionType = encType; - await expect(encryptService.rsaDecrypt(encString, privateKey)).rejects.toThrow( - "Invalid encryption type", - ); + await expect( + encryptService.decapsulateKeyUnsigned(encString, privateKey), + ).rejects.toThrow("Invalid encryption type"); }, ); it("decrypts data with provided key", async () => { cryptoFunctionService.rsaDecrypt.mockResolvedValue(data); - const actual = await encryptService.rsaDecrypt(makeEncString(data), privateKey); + const actual = await encryptService.decapsulateKeyUnsigned(makeEncString(data), privateKey); expect(cryptoFunctionService.rsaDecrypt).toBeCalledWith( expect.toEqualBuffer(data), @@ -478,7 +487,7 @@ describe("EncryptService", () => { "sha1", ); - expect(actual).toEqualBuffer(data); + expect(actual.key).toEqualBuffer(data); }); }); }); diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index bddcf6185f0..a2211753f4e 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -161,7 +161,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { deviceKeyEncryptedDevicePrivateKey, ] = await Promise.all([ // Encrypt user key with the DevicePublicKey - this.encryptService.rsaEncrypt(userKey.key, devicePublicKey), + this.encryptService.encapsulateKeyUnsigned(userKey, devicePublicKey), // Encrypt devicePublicKey with user key this.encryptService.encrypt(devicePublicKey, userKey), @@ -285,8 +285,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); // Encrypt the brand new user key with the now-decrypted public key for the device - const encryptedNewUserKey = await this.encryptService.rsaEncrypt( - newUserKey.key, + const encryptedNewUserKey = await this.encryptService.encapsulateKeyUnsigned( + newUserKey, decryptedDevicePublicKey, ); @@ -401,12 +401,12 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); // Attempt to decrypt encryptedUserDataKey with devicePrivateKey - const userKey = await this.encryptService.rsaDecrypt( + const userKey = await this.encryptService.decapsulateKeyUnsigned( new EncString(encryptedUserKey.encryptedString), devicePrivateKey, ); - return new SymmetricCryptoKey(userKey) as UserKey; + return userKey as UserKey; // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index 61c90c5b5ee..8431fe4cc35 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -416,7 +416,7 @@ describe("deviceTrustService", () => { .mockResolvedValue(mockUserKey); cryptoSvcRsaEncryptSpy = jest - .spyOn(encryptService, "rsaEncrypt") + .spyOn(encryptService, "encapsulateKeyUnsigned") .mockResolvedValue(mockDevicePublicKeyEncryptedUserKey); encryptServiceEncryptSpy = jest @@ -449,8 +449,8 @@ describe("deviceTrustService", () => { expect(cryptoSvcRsaEncryptSpy).toHaveBeenCalledTimes(1); // RsaEncrypt must be called w/ a user key array buffer of 64 bytes - const userKeyKey: Uint8Array = cryptoSvcRsaEncryptSpy.mock.calls[0][0]; - expect(userKeyKey.byteLength).toBe(64); + const userKey = cryptoSvcRsaEncryptSpy.mock.calls[0][0]; + expect(userKey.key.byteLength).toBe(64); expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2); @@ -610,7 +610,7 @@ describe("deviceTrustService", () => { mockUserId, mockEncryptedDevicePrivateKey, mockEncryptedUserKey, - mockDeviceKey, + null, ); expect(result).toBeNull(); @@ -621,8 +621,8 @@ describe("deviceTrustService", () => { .spyOn(encryptService, "decryptToBytes") .mockResolvedValue(new Uint8Array(userKeyBytesLength)); const rsaDecryptSpy = jest - .spyOn(encryptService, "rsaDecrypt") - .mockResolvedValue(new Uint8Array(userKeyBytesLength)); + .spyOn(encryptService, "decapsulateKeyUnsigned") + .mockResolvedValue(new SymmetricCryptoKey(new Uint8Array(userKeyBytesLength))); const result = await deviceTrustService.decryptUserKeyWithDeviceKey( mockUserId, @@ -863,9 +863,9 @@ describe("deviceTrustService", () => { }); // Mock the encryption of the new user key with the decrypted public key - encryptService.rsaEncrypt.mockImplementationOnce((data, publicKey) => { - expect(data.byteLength).toBe(64); // New key should also be 64 bytes - expect(new Uint8Array(data)[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1'; + encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => { + expect(data.key.byteLength).toBe(64); // New key should also be 64 bytes + expect(new Uint8Array(data.key)[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1'; expect(new Uint8Array(publicKey)[0]).toBe(FakeDecryptedPublicKeyMarker); return Promise.resolve(new EncString("4.ZW5jcnlwdGVkdXNlcg==")); diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 049ff7411c3..90d049b7293 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -557,8 +557,8 @@ describe("keyService", () => { return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey)); }); - encryptService.rsaDecrypt.mockImplementation((data, privateKey) => { - return Promise.resolve(fakeOrgKeyDecryption(data, privateKey)); + encryptService.decapsulateKeyUnsigned.mockImplementation((data, privateKey) => { + return Promise.resolve(new SymmetricCryptoKey(fakeOrgKeyDecryption(data, privateKey))); }); } diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index e4f07911661..baf1b86e160 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -493,7 +493,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { throw new Error("No public key found."); } - const encShareKey = await this.encryptService.rsaEncrypt(shareKey.key, publicKey); + const encShareKey = await this.encryptService.encapsulateKeyUnsigned(shareKey, publicKey); return [encShareKey, shareKey as T]; } @@ -968,11 +968,11 @@ export class DefaultKeyService implements KeyServiceAbstraction { return this.stateProvider.getUser(userId, USER_ENCRYPTED_PROVIDER_KEYS).state$.pipe( // Convert each value in the record to it's own decryption observable convertValues(async (_, value) => { - const decrypted = await this.encryptService.rsaDecrypt( + const decapsulatedKey = await this.encryptService.decapsulateKeyUnsigned( new EncString(value), userPrivateKey, ); - return new SymmetricCryptoKey(decrypted) as ProviderKey; + return decapsulatedKey as ProviderKey; }), // switchMap since there are no side effects switchMap((encryptedProviderKeys) => {