1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +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

@@ -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();

View File

@@ -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;

View File

@@ -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);

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 { 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);
}
}
}

View File

@@ -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,
);

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 { 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<UserKey> {
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 {