1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

[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>
This commit is contained in:
Bernd Schoolmann
2025-04-15 16:39:02 +02:00
committed by GitHub
parent 9f174e7723
commit b09305577f
30 changed files with 229 additions and 143 deletions

View File

@@ -71,7 +71,7 @@ export abstract class BaseBulkConfirmComponent implements OnInit {
if (publicKey == null) { if (publicKey == null) {
continue; continue;
} }
const encryptedKey = await this.encryptService.rsaEncrypt(key.key, publicKey); const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(key, publicKey);
userIdsWithKeys.push({ userIdsWithKeys.push({
id: user.id, id: user.id,
key: encryptedKey.encryptedString, key: encryptedKey.encryptedString,

View File

@@ -322,7 +322,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise<void> { async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise<void> {
const orgKey = await this.keyService.getOrgKey(this.organization.id); 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(); const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString; request.key = key.encryptedString;
await this.organizationUserApiService.postOrganizationUserConfirm( await this.organizationUserApiService.postOrganizationUserConfirm(

View File

@@ -89,7 +89,7 @@ describe("OrganizationUserResetPasswordService", () => {
}), }),
); );
encryptService.rsaEncrypt.mockResolvedValue( encryptService.encapsulateKeyUnsigned.mockResolvedValue(
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"), new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"),
); );
}); });
@@ -111,7 +111,10 @@ describe("OrganizationUserResetPasswordService", () => {
it("should rsa encrypt the user key", async () => { it("should rsa encrypt the user key", async () => {
await sut.buildRecoveryKey(mockOrgId, mockUserKey, mockPublicKeys); 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 () => { 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"), publicKey: Utils.fromUtf8ToArray("test-public-key"),
}), }),
); );
encryptService.rsaEncrypt.mockResolvedValue( encryptService.encapsulateKeyUnsigned.mockResolvedValue(
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"), new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "mockEncryptedUserKey"),
); );
}); });

View File

@@ -14,7 +14,6 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; 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 { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key"; import { UserKey } from "@bitwarden/common/types/key";
import { import {
@@ -59,6 +58,10 @@ export class OrganizationUserResetPasswordService
userKey: UserKey, userKey: UserKey,
trustedPublicKeys: Uint8Array[], trustedPublicKeys: Uint8Array[],
): Promise<EncryptedString> { ): Promise<EncryptedString> {
if (userKey == null) {
throw new Error("User key is required for recovery.");
}
// Retrieve Public Key // Retrieve Public Key
const orgKeys = await this.organizationApiService.getKeys(orgId); const orgKeys = await this.organizationApiService.getKeys(orgId);
if (orgKeys == null) { if (orgKeys == null) {
@@ -76,7 +79,8 @@ export class OrganizationUserResetPasswordService
} }
// RSA Encrypt user key with organization's public key // 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; return encryptedKey.encryptedString;
} }
@@ -114,11 +118,11 @@ export class OrganizationUserResetPasswordService
); );
// Decrypt User's Reset Password Key to get UserKey // 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), new EncString(response.resetPasswordKey),
decPrivateKey, decPrivateKey,
); );
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey; const existingUserKey = userKey as UserKey;
// determine Kdf Algorithm // determine Kdf Algorithm
const kdfConfig: KdfConfig = const kdfConfig: KdfConfig =

View File

@@ -35,7 +35,7 @@ describe("RotateableKeySetService", () => {
const encryptedPrivateKey = Symbol(); const encryptedPrivateKey = Symbol();
keyService.makeKeyPair.mockResolvedValue(["publicKey", encryptedPrivateKey as any]); keyService.makeKeyPair.mockResolvedValue(["publicKey", encryptedPrivateKey as any]);
keyService.getUserKey.mockResolvedValue({ key: userKey.key } 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); encryptService.encrypt.mockResolvedValue(encryptedPublicKey as any);
const result = await service.createKeySet(externalKey as any); const result = await service.createKeySet(externalKey as any);

View File

@@ -25,7 +25,10 @@ export class RotateableKeySetService {
const userKey = await this.keyService.getUserKey(); const userKey = await this.keyService.getUserKey();
const rawPublicKey = Utils.fromB64ToArray(publicKey); 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); const encryptedPublicKey = await this.encryptService.encrypt(rawPublicKey, userKey);
return new RotateableKeySet(encryptedUserKey, encryptedPublicKey, encryptedPrivateKey); 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"); throw new Error("failed to rotate key set: could not decrypt public key");
} }
const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); 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<ExternalKey>( const newRotateableKeySet = new RotateableKeySet<ExternalKey>(
newEncryptedUserKey, newEncryptedUserKey,

View File

@@ -130,7 +130,9 @@ describe("EmergencyAccessService", () => {
keyService.getUserKey.mockResolvedValueOnce(mockUserKey); keyService.getUserKey.mockResolvedValueOnce(mockUserKey);
encryptService.rsaEncrypt.mockResolvedValueOnce(mockUserPublicKeyEncryptedUserKey); encryptService.encapsulateKeyUnsigned.mockResolvedValueOnce(
mockUserPublicKeyEncryptedUserKey,
);
emergencyAccessApiService.postEmergencyAccessConfirm.mockResolvedValueOnce(); emergencyAccessApiService.postEmergencyAccessConfirm.mockResolvedValueOnce();
@@ -160,7 +162,9 @@ describe("EmergencyAccessService", () => {
const mockDecryptedGrantorUserKey = new Uint8Array(64); const mockDecryptedGrantorUserKey = new Uint8Array(64);
keyService.getPrivateKey.mockResolvedValue(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; const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey;
@@ -253,7 +257,7 @@ describe("EmergencyAccessService", () => {
publicKey: Utils.fromUtf8ToB64("trustedPublicKey"), publicKey: Utils.fromUtf8ToB64("trustedPublicKey"),
} as UserKeyResponse); } as UserKeyResponse);
encryptService.rsaEncrypt.mockImplementation((plainValue, publicKey) => { encryptService.encapsulateKeyUnsigned.mockImplementation((plainValue, publicKey) => {
return Promise.resolve( return Promise.resolve(
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue), new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue),
); );

View File

@@ -12,7 +12,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; 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 { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key"; import { UserKey } from "@bitwarden/common/types/key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; 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."); 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), new EncString(response.keyEncrypted),
activeUserPrivateKey, activeUserPrivateKey,
); )) as UserKey;
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey;
let ciphers: CipherView[] = []; let ciphers: CipherView[] = [];
if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) { 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."); 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), new EncString(takeoverResponse.keyEncrypted),
activeUserPrivateKey, activeUserPrivateKey,
); );
if (grantorKeyBuffer == null) { if (grantorKey == null) {
throw new Error("Failed to decrypt grantor key"); throw new Error("Failed to decrypt grantor key");
} }
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey; const grantorUserKey = grantorKey as UserKey;
let config: KdfConfig; let config: KdfConfig;
@@ -407,6 +405,6 @@ export class EmergencyAccessService
} }
private async encryptKey(userKey: UserKey, publicKey: Uint8Array): Promise<EncryptedString> { private async encryptKey(userKey: UserKey, publicKey: Uint8Array): Promise<EncryptedString> {
return (await this.encryptService.rsaEncrypt(userKey.key, publicKey)).encryptedString; return (await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey)).encryptedString;
} }
} }

View File

@@ -196,7 +196,7 @@ describe("AcceptOrganizationInviteService", () => {
); );
accountService.activeAccount$ = new BehaviorSubject({ id: "activeUserId" }) as any; accountService.activeAccount$ = new BehaviorSubject({ id: "activeUserId" }) as any;
keyService.userKey$.mockReturnValue(new BehaviorSubject({ key: "userKey" } as any)); keyService.userKey$.mockReturnValue(new BehaviorSubject({ key: "userKey" } as any));
encryptService.rsaEncrypt.mockResolvedValue({ encryptService.encapsulateKeyUnsigned.mockResolvedValue({
encryptedString: "encryptedString", encryptedString: "encryptedString",
} as EncString); } as EncString);
@@ -218,8 +218,8 @@ describe("AcceptOrganizationInviteService", () => {
expect(result).toBe(true); expect(result).toBe(true);
expect(OrganizationTrustComponent.open).toHaveBeenCalled(); expect(OrganizationTrustComponent.open).toHaveBeenCalled();
expect(encryptService.rsaEncrypt).toHaveBeenCalledWith( expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(
"userKey", { key: "userKey" },
Utils.fromB64ToArray("publicKey"), Utils.fromB64ToArray("publicKey"),
); );
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled(); expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();

View File

@@ -202,7 +202,7 @@ export class AcceptOrganizationInviteService {
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$)).id; const activeUserId = (await firstValueFrom(this.accountService.activeAccount$)).id;
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
// RSA Encrypt user's encKey.key with organization public key // 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 // Add reset password key to accept request
request.resetPasswordKey = encryptedKey.encryptedString; request.resetPasswordKey = encryptedKey.encryptedString;

View File

@@ -7,6 +7,7 @@ import {
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; 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 { KeyService } from "@bitwarden/key-management";
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
@@ -124,8 +125,10 @@ describe("OrganizationAuthRequestService", () => {
); );
const encryptedUserKey = new EncString("encryptedUserKey"); const encryptedUserKey = new EncString("encryptedUserKey");
encryptService.rsaDecrypt.mockResolvedValue(new Uint8Array(32)); encryptService.decapsulateKeyUnsigned.mockResolvedValue(
encryptService.rsaEncrypt.mockResolvedValue(encryptedUserKey); new SymmetricCryptoKey(new Uint8Array(32)),
);
encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey);
const mockPendingAuthRequest = new PendingAuthRequestView(); const mockPendingAuthRequest = new PendingAuthRequestView();
mockPendingAuthRequest.id = "requestId1"; mockPendingAuthRequest.id = "requestId1";
@@ -166,8 +169,10 @@ describe("OrganizationAuthRequestService", () => {
); );
const encryptedUserKey = new EncString("encryptedUserKey"); const encryptedUserKey = new EncString("encryptedUserKey");
encryptService.rsaDecrypt.mockResolvedValue(new Uint8Array(32)); encryptService.decapsulateKeyUnsigned.mockResolvedValue(
encryptService.rsaEncrypt.mockResolvedValue(encryptedUserKey); new SymmetricCryptoKey(new Uint8Array(32)),
);
encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey);
const mockPendingAuthRequest = new PendingAuthRequestView(); const mockPendingAuthRequest = new PendingAuthRequestView();
mockPendingAuthRequest.id = "requestId1"; mockPendingAuthRequest.id = "requestId1";

View File

@@ -7,7 +7,6 @@ import {
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; 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 { KeyService } from "@bitwarden/key-management";
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
@@ -119,13 +118,12 @@ export class OrganizationAuthRequestService {
); );
// Decrypt user key with decrypted org private key // Decrypt user key with decrypted org private key
const decValue = await this.encryptService.rsaDecrypt( const userKey = await this.encryptService.decapsulateKeyUnsigned(
new EncString(encryptedUserKey), new EncString(encryptedUserKey),
decOrgPrivateKey, decOrgPrivateKey,
); );
const userKey = new SymmetricCryptoKey(decValue);
// Re-encrypt user Key with the Device Public Key // Re-encrypt user Key with the Device Public Key
return await this.encryptService.rsaEncrypt(userKey.key, devicePubKey); return await this.encryptService.encapsulateKeyUnsigned(userKey, devicePubKey);
} }
} }

View File

@@ -187,7 +187,7 @@ export class MembersComponent extends BaseMembersComponent<ProviderUser> {
async confirmUser(user: ProviderUser, publicKey: Uint8Array): Promise<void> { async confirmUser(user: ProviderUser, publicKey: Uint8Array): Promise<void> {
const providerKey = await this.keyService.getProviderKey(this.providerId); 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(); const request = new ProviderUserConfirmRequest();
request.key = key.encryptedString; request.key = key.encryptedString;
await this.apiService.postProviderUserConfirm(this.providerId, user.id, request); await this.apiService.postProviderUserConfirm(this.providerId, user.id, request);

View File

@@ -211,7 +211,10 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
// RSA Encrypt user key with organization public key // RSA Encrypt user key with organization public key
const userKey = await this.keyService.getUserKey(); 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(); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterPasswordHash; resetRequest.masterPasswordHash = masterPasswordHash;

View File

@@ -174,7 +174,7 @@ describe("DefaultSetPasswordJitService", () => {
} }
keyService.userKey$.mockReturnValue(of(userKey)); keyService.userKey$.mockReturnValue(of(userKey));
encryptService.rsaEncrypt.mockResolvedValue(userKeyEncString); encryptService.encapsulateKeyUnsigned.mockResolvedValue(userKeyEncString);
organizationUserApiService.putOrganizationUserResetPasswordEnrollment.mockResolvedValue( organizationUserApiService.putOrganizationUserResetPasswordEnrollment.mockResolvedValue(
undefined, undefined,
@@ -216,7 +216,7 @@ describe("DefaultSetPasswordJitService", () => {
// Assert // Assert
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
expect(organizationApiService.getKeys).toHaveBeenCalledWith(orgId); expect(organizationApiService.getKeys).toHaveBeenCalledWith(orgId);
expect(encryptService.rsaEncrypt).toHaveBeenCalledWith(userKey.key, orgPublicKey); expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(userKey, orgPublicKey);
expect( expect(
organizationUserApiService.putOrganizationUserResetPasswordEnrollment, organizationUserApiService.putOrganizationUserResetPasswordEnrollment,
).toHaveBeenCalled(); ).toHaveBeenCalled();

View File

@@ -161,7 +161,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
throw new Error("userKey not found. Could not handle reset password auto enroll."); 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(); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterKeyHash; resetRequest.masterPasswordHash = masterKeyHash;

View File

@@ -231,7 +231,9 @@ describe("WebAuthnLoginStrategy", () => {
const mockUserKey = new SymmetricCryptoKey(mockUserKeyArray) as UserKey; const mockUserKey = new SymmetricCryptoKey(mockUserKeyArray) as UserKey;
encryptService.decryptToBytes.mockResolvedValue(mockPrfPrivateKey); encryptService.decryptToBytes.mockResolvedValue(mockPrfPrivateKey);
encryptService.rsaDecrypt.mockResolvedValue(mockUserKeyArray); encryptService.decapsulateKeyUnsigned.mockResolvedValue(
new SymmetricCryptoKey(mockUserKeyArray),
);
// Act // Act
await webAuthnLoginStrategy.logIn(webAuthnCredentials); await webAuthnLoginStrategy.logIn(webAuthnCredentials);
@@ -249,8 +251,8 @@ describe("WebAuthnLoginStrategy", () => {
idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedPrivateKey, idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedPrivateKey,
webAuthnCredentials.prfKey, webAuthnCredentials.prfKey,
); );
expect(encryptService.rsaDecrypt).toHaveBeenCalledTimes(1); expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledTimes(1);
expect(encryptService.rsaDecrypt).toHaveBeenCalledWith( expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledWith(
idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedUserKey, idTokenResponse.userDecryptionOptions.webAuthnPrfOption.encryptedUserKey,
mockPrfPrivateKey, mockPrfPrivateKey,
); );
@@ -278,7 +280,7 @@ describe("WebAuthnLoginStrategy", () => {
// Assert // Assert
expect(encryptService.decryptToBytes).not.toHaveBeenCalled(); expect(encryptService.decryptToBytes).not.toHaveBeenCalled();
expect(encryptService.rsaDecrypt).not.toHaveBeenCalled(); expect(encryptService.decapsulateKeyUnsigned).not.toHaveBeenCalled();
expect(keyService.setUserKey).not.toHaveBeenCalled(); expect(keyService.setUserKey).not.toHaveBeenCalled();
}); });
@@ -330,7 +332,7 @@ describe("WebAuthnLoginStrategy", () => {
apiService.postIdentityToken.mockResolvedValue(idTokenResponse); apiService.postIdentityToken.mockResolvedValue(idTokenResponse);
encryptService.rsaDecrypt.mockResolvedValue(null); encryptService.decapsulateKeyUnsigned.mockResolvedValue(null);
// Act // Act
await webAuthnLoginStrategy.logIn(webAuthnCredentials); await webAuthnLoginStrategy.logIn(webAuthnCredentials);

View File

@@ -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 { 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 { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; 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 { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key"; import { UserKey } from "@bitwarden/common/types/key";
@@ -89,13 +88,13 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
); );
// decrypt user key with private key // decrypt user key with private key
const userKey = await this.encryptService.rsaDecrypt( const userKey = await this.encryptService.decapsulateKeyUnsigned(
new EncString(webAuthnPrfOption.encryptedUserKey.encryptedString), new EncString(webAuthnPrfOption.encryptedUserKey.encryptedString),
privateKey, privateKey,
); );
if (userKey) { if (userKey) {
await this.keyService.setUserKey(new SymmetricCryptoKey(userKey) as UserKey, userId); await this.keyService.setUserKey(userKey as UserKey, userId);
} }
} }
} }

View File

@@ -88,6 +88,9 @@ describe("AuthRequestService", () => {
encryptService.rsaEncrypt.mockResolvedValue({ encryptService.rsaEncrypt.mockResolvedValue({
encryptedString: "ENCRYPTED_STRING", encryptedString: "ENCRYPTED_STRING",
} as EncString); } as EncString);
encryptService.encapsulateKeyUnsigned.mockResolvedValue({
encryptedString: "ENCRYPTED_STRING",
} as EncString);
appIdService.getAppId.mockResolvedValue("APP_ID"); appIdService.getAppId.mockResolvedValue("APP_ID");
}); });
it("should throw if auth request is missing id or key", async () => { it("should throw if auth request is missing id or key", async () => {
@@ -111,7 +114,10 @@ describe("AuthRequestService", () => {
new AuthRequestResponse({ id: "123", publicKey: "KEY" }), 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 () => { 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" }), 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", () => { describe("setUserKeyAfterDecryptingSharedUserKey", () => {
@@ -214,7 +223,9 @@ describe("AuthRequestService", () => {
const mockDecryptedUserKeyBytes = new Uint8Array(64); const mockDecryptedUserKeyBytes = new Uint8Array(64);
const mockDecryptedUserKey = new SymmetricCryptoKey(mockDecryptedUserKeyBytes) as UserKey; const mockDecryptedUserKey = new SymmetricCryptoKey(mockDecryptedUserKeyBytes) as UserKey;
encryptService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedUserKeyBytes); encryptService.decapsulateKeyUnsigned.mockResolvedValueOnce(
new SymmetricCryptoKey(mockDecryptedUserKeyBytes),
);
// Act // Act
const result = await sut.decryptPubKeyEncryptedUserKey( const result = await sut.decryptPubKeyEncryptedUserKey(
@@ -223,7 +234,7 @@ describe("AuthRequestService", () => {
); );
// Assert // Assert
expect(encryptService.rsaDecrypt).toBeCalledWith( expect(encryptService.decapsulateKeyUnsigned).toBeCalledWith(
new EncString(mockPubKeyEncryptedUserKey), new EncString(mockPubKeyEncryptedUserKey),
mockPrivateKey, mockPrivateKey,
); );
@@ -244,9 +255,10 @@ describe("AuthRequestService", () => {
const mockDecryptedMasterKeyHashBytes = new Uint8Array(64); const mockDecryptedMasterKeyHashBytes = new Uint8Array(64);
const mockDecryptedMasterKeyHash = Utils.fromBufferToUtf8(mockDecryptedMasterKeyHashBytes); const mockDecryptedMasterKeyHash = Utils.fromBufferToUtf8(mockDecryptedMasterKeyHashBytes);
encryptService.rsaDecrypt encryptService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedMasterKeyHashBytes);
.mockResolvedValueOnce(mockDecryptedMasterKeyBytes) encryptService.decapsulateKeyUnsigned.mockResolvedValueOnce(
.mockResolvedValueOnce(mockDecryptedMasterKeyHashBytes); new SymmetricCryptoKey(mockDecryptedMasterKeyBytes),
);
// Act // Act
const result = await sut.decryptPubKeyEncryptedMasterKeyAndHash( const result = await sut.decryptPubKeyEncryptedMasterKeyAndHash(
@@ -256,13 +268,11 @@ describe("AuthRequestService", () => {
); );
// Assert // Assert
expect(encryptService.rsaDecrypt).toHaveBeenNthCalledWith( expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledWith(
1,
new EncString(mockPubKeyEncryptedMasterKey), new EncString(mockPubKeyEncryptedMasterKey),
mockPrivateKey, mockPrivateKey,
); );
expect(encryptService.rsaDecrypt).toHaveBeenNthCalledWith( expect(encryptService.rsaDecrypt).toHaveBeenCalledWith(
2,
new EncString(mockPubKeyEncryptedMasterKeyHash), new EncString(mockPubKeyEncryptedMasterKeyHash),
mockPrivateKey, mockPrivateKey,
); );

View File

@@ -14,7 +14,6 @@ import { AuthRequestPushNotification } from "@bitwarden/common/models/response/n
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { import {
AUTH_REQUEST_DISK_LOCAL, AUTH_REQUEST_DISK_LOCAL,
StateProvider, StateProvider,
@@ -116,13 +115,12 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
Utils.fromUtf8ToArray(masterKeyHash), Utils.fromUtf8ToArray(masterKeyHash),
pubKey, pubKey,
); );
keyToEncrypt = masterKey.encKey; keyToEncrypt = masterKey;
} else { } else {
const userKey = await this.keyService.getUserKey(); keyToEncrypt = await this.keyService.getUserKey();
keyToEncrypt = userKey.key;
} }
const encryptedKey = await this.encryptService.rsaEncrypt(keyToEncrypt, pubKey); const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(keyToEncrypt, pubKey);
const response = new PasswordlessAuthRequest( const response = new PasswordlessAuthRequest(
encryptedKey.encryptedString, encryptedKey.encryptedString,
@@ -171,12 +169,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
pubKeyEncryptedUserKey: string, pubKeyEncryptedUserKey: string,
privateKey: Uint8Array, privateKey: Uint8Array,
): Promise<UserKey> { ): Promise<UserKey> {
const decryptedUserKeyBytes = await this.encryptService.rsaDecrypt( return (await this.encryptService.decapsulateKeyUnsigned(
new EncString(pubKeyEncryptedUserKey), new EncString(pubKeyEncryptedUserKey),
privateKey, privateKey,
); )) as UserKey;
return new SymmetricCryptoKey(decryptedUserKeyBytes) as UserKey;
} }
async decryptPubKeyEncryptedMasterKeyAndHash( async decryptPubKeyEncryptedMasterKeyAndHash(
@@ -184,17 +180,15 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
pubKeyEncryptedMasterKeyHash: string, pubKeyEncryptedMasterKeyHash: string,
privateKey: Uint8Array, privateKey: Uint8Array,
): Promise<{ masterKey: MasterKey; masterKeyHash: string }> { ): Promise<{ masterKey: MasterKey; masterKeyHash: string }> {
const decryptedMasterKeyArrayBuffer = await this.encryptService.rsaDecrypt( const masterKey = (await this.encryptService.decapsulateKeyUnsigned(
new EncString(pubKeyEncryptedMasterKey), new EncString(pubKeyEncryptedMasterKey),
privateKey, privateKey,
); )) as MasterKey;
const decryptedMasterKeyHashArrayBuffer = await this.encryptService.rsaDecrypt( const decryptedMasterKeyHashArrayBuffer = await this.encryptService.rsaDecrypt(
new EncString(pubKeyEncryptedMasterKeyHash), new EncString(pubKeyEncryptedMasterKeyHash),
privateKey, privateKey,
); );
const masterKey = new SymmetricCryptoKey(decryptedMasterKeyArrayBuffer) as MasterKey;
const masterKeyHash = Utils.fromBufferToUtf8(decryptedMasterKeyHashArrayBuffer); const masterKeyHash = Utils.fromBufferToUtf8(decryptedMasterKeyHashArrayBuffer);
return { return {

View File

@@ -31,8 +31,10 @@ export class EncryptedOrganizationKey implements BaseEncryptedOrganizationKey {
constructor(private key: string) {} constructor(private key: string) {}
async decrypt(encryptService: EncryptService, privateKey: UserPrivateKey) { async decrypt(encryptService: EncryptService, privateKey: UserPrivateKey) {
const decValue = await encryptService.rsaDecrypt(this.encryptedOrganizationKey, privateKey); return (await encryptService.decapsulateKeyUnsigned(
return new SymmetricCryptoKey(decValue) as OrgKey; this.encryptedOrganizationKey,
privateKey,
)) as OrgKey;
} }
get encryptedOrganizationKey() { get encryptedOrganizationKey() {

View File

@@ -100,7 +100,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
activeAccountSubject.next(Object.assign(user1AccountInfo, { id: "userId" as UserId })); activeAccountSubject.next(Object.assign(user1AccountInfo, { id: "userId" as UserId }));
keyService.getUserKey.mockResolvedValue({ key: "key" } as any); keyService.getUserKey.mockResolvedValue({ key: "key" } as any);
encryptService.rsaEncrypt.mockResolvedValue(encryptedKey as any); encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedKey as any);
await service.enroll("orgId"); await service.enroll("orgId");
@@ -122,7 +122,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
}; };
const encryptedKey = { encryptedString: "encryptedString" }; const encryptedKey = { encryptedString: "encryptedString" };
organizationApiService.getKeys.mockResolvedValue(orgKeyResponse as any); 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); await service.enroll("orgId", "userId", { key: "key" } as any);

View File

@@ -51,7 +51,7 @@ export class PasswordResetEnrollmentServiceImplementation
userId ?? (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)))); userId ?? (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))));
userKey = userKey ?? (await this.keyService.getUserKey(userId)); userKey = userKey ?? (await this.keyService.getUserKey(userId));
// RSA Encrypt user's userKey.key with organization public key // 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(); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString; resetRequest.resetPasswordKey = encryptedKey.encryptedString;

View File

@@ -35,7 +35,38 @@ export abstract class EncryptService {
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
decryptTrace?: string, decryptTrace?: string,
): Promise<Uint8Array | null>; ): Promise<Uint8Array | null>;
/**
* 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<EncString>;
/**
* 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<SymmetricCryptoKey>;
/**
* @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<EncString>; abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString>;
/**
* @deprecated Use decapsulateKeyUnsigned instead
* @param data - The ciphertext to decrypt
* @param privateKey - The privateKey to decrypt with
*/
abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array>; abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array>;
/** /**
* @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed

View File

@@ -235,42 +235,22 @@ export class EncryptServiceImplementation implements EncryptService {
} }
} }
async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString> { async encapsulateKeyUnsigned(
if (data == null) { sharedKey: SymmetricCryptoKey,
throw new Error("No data provided for encryption."); encapsulationKey: Uint8Array,
): Promise<EncString> {
if (sharedKey == null) {
throw new Error("No sharedKey provided for encapsulation");
}
return await this.rsaEncrypt(sharedKey.toEncoded(), encapsulationKey);
} }
if (publicKey == null) { async decapsulateKeyUnsigned(
throw new Error("No public key provided for encryption."); encryptedSharedKey: EncString,
} decapsulationKey: Uint8Array,
const encrypted = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1"); ): Promise<SymmetricCryptoKey> {
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encrypted)); const keyBytes = await this.rsaDecrypt(encryptedSharedKey, decapsulationKey);
} return new SymmetricCryptoKey(keyBytes);
async rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array> {
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);
} }
/** /**
@@ -341,4 +321,42 @@ export class EncryptServiceImplementation implements EncryptService {
this.logDecryptError(msg, keyEncType, dataEncType, decryptContext); this.logDecryptError(msg, keyEncType, dataEncType, decryptContext);
} }
} }
async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString> {
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<Uint8Array> {
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);
}
} }

View File

@@ -412,7 +412,8 @@ describe("EncryptService", () => {
}); });
describe("rsa", () => { describe("rsa", () => {
const data = makeStaticByteArray(10, 100); const data = makeStaticByteArray(64, 100);
const testKey = new SymmetricCryptoKey(data);
const encryptedData = makeStaticByteArray(10, 150); const encryptedData = makeStaticByteArray(10, 150);
const publicKey = makeStaticByteArray(10, 200); const publicKey = makeStaticByteArray(10, 200);
const privateKey = makeStaticByteArray(10, 250); const privateKey = makeStaticByteArray(10, 250);
@@ -422,22 +423,26 @@ describe("EncryptService", () => {
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(data)); return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(data));
} }
describe("rsaEncrypt", () => { describe("encapsulateKeyUnsigned", () => {
it("throws if no data is provided", () => { 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", () => { 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 () => { it("encrypts data with provided key", async () => {
cryptoFunctionService.rsaEncrypt.mockResolvedValue(encryptedData); cryptoFunctionService.rsaEncrypt.mockResolvedValue(encryptedData);
const actual = await encryptService.rsaEncrypt(data, publicKey); const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey);
expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith( expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith(
expect.toEqualBuffer(data), expect.toEqualBuffer(testKey.key),
expect.toEqualBuffer(publicKey), expect.toEqualBuffer(publicKey),
"sha1", "sha1",
); );
@@ -447,13 +452,17 @@ describe("EncryptService", () => {
}); });
}); });
describe("rsaDecrypt", () => { describe("decapsulateKeyUnsigned", () => {
it("throws if no data is provided", () => { 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", () => { 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])( it.each([EncryptionType.AesCbc256_B64, EncryptionType.AesCbc256_HmacSha256_B64])(
@@ -461,16 +470,16 @@ describe("EncryptService", () => {
async (encType) => { async (encType) => {
encString.encryptionType = encType; encString.encryptionType = encType;
await expect(encryptService.rsaDecrypt(encString, privateKey)).rejects.toThrow( await expect(
"Invalid encryption type", encryptService.decapsulateKeyUnsigned(encString, privateKey),
); ).rejects.toThrow("Invalid encryption type");
}, },
); );
it("decrypts data with provided key", async () => { it("decrypts data with provided key", async () => {
cryptoFunctionService.rsaDecrypt.mockResolvedValue(data); 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(cryptoFunctionService.rsaDecrypt).toBeCalledWith(
expect.toEqualBuffer(data), expect.toEqualBuffer(data),
@@ -478,7 +487,7 @@ describe("EncryptService", () => {
"sha1", "sha1",
); );
expect(actual).toEqualBuffer(data); expect(actual.key).toEqualBuffer(data);
}); });
}); });
}); });

View File

@@ -161,7 +161,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
deviceKeyEncryptedDevicePrivateKey, deviceKeyEncryptedDevicePrivateKey,
] = await Promise.all([ ] = await Promise.all([
// Encrypt user key with the DevicePublicKey // Encrypt user key with the DevicePublicKey
this.encryptService.rsaEncrypt(userKey.key, devicePublicKey), this.encryptService.encapsulateKeyUnsigned(userKey, devicePublicKey),
// Encrypt devicePublicKey with user key // Encrypt devicePublicKey with user key
this.encryptService.encrypt(devicePublicKey, userKey), 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 // Encrypt the brand new user key with the now-decrypted public key for the device
const encryptedNewUserKey = await this.encryptService.rsaEncrypt( const encryptedNewUserKey = await this.encryptService.encapsulateKeyUnsigned(
newUserKey.key, newUserKey,
decryptedDevicePublicKey, decryptedDevicePublicKey,
); );
@@ -401,12 +401,12 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
); );
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey // Attempt to decrypt encryptedUserDataKey with devicePrivateKey
const userKey = await this.encryptService.rsaDecrypt( const userKey = await this.encryptService.decapsulateKeyUnsigned(
new EncString(encryptedUserKey.encryptedString), new EncString(encryptedUserKey.encryptedString),
devicePrivateKey, devicePrivateKey,
); );
return new SymmetricCryptoKey(userKey) as UserKey; return userKey as UserKey;
// FIXME: Remove when updating file. Eslint update // FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) { } catch (e) {

View File

@@ -416,7 +416,7 @@ describe("deviceTrustService", () => {
.mockResolvedValue(mockUserKey); .mockResolvedValue(mockUserKey);
cryptoSvcRsaEncryptSpy = jest cryptoSvcRsaEncryptSpy = jest
.spyOn(encryptService, "rsaEncrypt") .spyOn(encryptService, "encapsulateKeyUnsigned")
.mockResolvedValue(mockDevicePublicKeyEncryptedUserKey); .mockResolvedValue(mockDevicePublicKeyEncryptedUserKey);
encryptServiceEncryptSpy = jest encryptServiceEncryptSpy = jest
@@ -449,8 +449,8 @@ describe("deviceTrustService", () => {
expect(cryptoSvcRsaEncryptSpy).toHaveBeenCalledTimes(1); expect(cryptoSvcRsaEncryptSpy).toHaveBeenCalledTimes(1);
// RsaEncrypt must be called w/ a user key array buffer of 64 bytes // RsaEncrypt must be called w/ a user key array buffer of 64 bytes
const userKeyKey: Uint8Array = cryptoSvcRsaEncryptSpy.mock.calls[0][0]; const userKey = cryptoSvcRsaEncryptSpy.mock.calls[0][0];
expect(userKeyKey.byteLength).toBe(64); expect(userKey.key.byteLength).toBe(64);
expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2); expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2);
@@ -610,7 +610,7 @@ describe("deviceTrustService", () => {
mockUserId, mockUserId,
mockEncryptedDevicePrivateKey, mockEncryptedDevicePrivateKey,
mockEncryptedUserKey, mockEncryptedUserKey,
mockDeviceKey, null,
); );
expect(result).toBeNull(); expect(result).toBeNull();
@@ -621,8 +621,8 @@ describe("deviceTrustService", () => {
.spyOn(encryptService, "decryptToBytes") .spyOn(encryptService, "decryptToBytes")
.mockResolvedValue(new Uint8Array(userKeyBytesLength)); .mockResolvedValue(new Uint8Array(userKeyBytesLength));
const rsaDecryptSpy = jest const rsaDecryptSpy = jest
.spyOn(encryptService, "rsaDecrypt") .spyOn(encryptService, "decapsulateKeyUnsigned")
.mockResolvedValue(new Uint8Array(userKeyBytesLength)); .mockResolvedValue(new SymmetricCryptoKey(new Uint8Array(userKeyBytesLength)));
const result = await deviceTrustService.decryptUserKeyWithDeviceKey( const result = await deviceTrustService.decryptUserKeyWithDeviceKey(
mockUserId, mockUserId,
@@ -863,9 +863,9 @@ describe("deviceTrustService", () => {
}); });
// Mock the encryption of the new user key with the decrypted public key // Mock the encryption of the new user key with the decrypted public key
encryptService.rsaEncrypt.mockImplementationOnce((data, publicKey) => { encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => {
expect(data.byteLength).toBe(64); // New key should also be 64 bytes expect(data.key.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'; expect(new Uint8Array(data.key)[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1';
expect(new Uint8Array(publicKey)[0]).toBe(FakeDecryptedPublicKeyMarker); expect(new Uint8Array(publicKey)[0]).toBe(FakeDecryptedPublicKeyMarker);
return Promise.resolve(new EncString("4.ZW5jcnlwdGVkdXNlcg==")); return Promise.resolve(new EncString("4.ZW5jcnlwdGVkdXNlcg=="));

View File

@@ -557,8 +557,8 @@ describe("keyService", () => {
return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey)); return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey));
}); });
encryptService.rsaDecrypt.mockImplementation((data, privateKey) => { encryptService.decapsulateKeyUnsigned.mockImplementation((data, privateKey) => {
return Promise.resolve(fakeOrgKeyDecryption(data, privateKey)); return Promise.resolve(new SymmetricCryptoKey(fakeOrgKeyDecryption(data, privateKey)));
}); });
} }

View File

@@ -493,7 +493,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
throw new Error("No public key found."); 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]; 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( return this.stateProvider.getUser(userId, USER_ENCRYPTED_PROVIDER_KEYS).state$.pipe(
// Convert each value in the record to it's own decryption observable // Convert each value in the record to it's own decryption observable
convertValues(async (_, value) => { convertValues(async (_, value) => {
const decrypted = await this.encryptService.rsaDecrypt( const decapsulatedKey = await this.encryptService.decapsulateKeyUnsigned(
new EncString(value), new EncString(value),
userPrivateKey, userPrivateKey,
); );
return new SymmetricCryptoKey(decrypted) as ProviderKey; return decapsulatedKey as ProviderKey;
}), }),
// switchMap since there are no side effects // switchMap since there are no side effects
switchMap((encryptedProviderKeys) => { switchMap((encryptedProviderKeys) => {