From c56b50d4760c13881caaa3aba3a08627ceed9a74 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 12 Dec 2025 14:42:53 +0100 Subject: [PATCH] Remove deprecated master key login with device flow --- .../login-via-auth-request.component.ts | 27 ++------- .../auth-request.service.abstraction.ts | 25 +-------- .../sso-login.strategy.spec.ts | 21 ------- .../login-strategies/sso-login.strategy.ts | 22 ++------ .../auth-request/auth-request.service.spec.ts | 56 +------------------ .../auth-request/auth-request.service.ts | 48 +--------------- .../models/response/auth-request.response.ts | 4 +- .../crypto/abstractions/encrypt.service.ts | 7 --- .../encrypt.service.implementation.ts | 20 ------- 9 files changed, 15 insertions(+), 215 deletions(-) diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index 040d4d3c121..fee5a5c3a07 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -679,27 +679,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { privateKey: ArrayBuffer, userId: UserId, ): Promise { - /** - * [Flow Type Detection] - * We determine the type of `key` based on the presence or absence of `masterPasswordHash`: - * - If `masterPasswordHash` exists: Standard Flow 1 or 3 (device has masterKey) - * - If no `masterPasswordHash`: Standard Flow 2, 4, or Admin Flow (device sends userKey) - */ - if (authRequestResponse.masterPasswordHash) { - // [Standard Flow 1 or 3] Device has masterKey - await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( - authRequestResponse, - privateKey, - userId, - ); - } else { - // [Standard Flow 2, 4, or Admin Flow] Device sends userKey - await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( - authRequestResponse, - privateKey, - userId, - ); - } + // [Standard Flow 2, 4, or Admin Flow] Device sends userKey + await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( + authRequestResponse, + privateKey, + userId, + ); // [Admin Flow Cleanup] Clear one-time use admin auth request // clear the admin auth request from state so it cannot be used again (it's a one time use) diff --git a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts index 1bfbfd8d004..1331ead3d64 100644 --- a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts @@ -4,7 +4,7 @@ import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/a import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response"; import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey, MasterKey } from "@bitwarden/common/types/key"; +import { UserKey } from "@bitwarden/common/types/key"; export abstract class AuthRequestServiceAbstraction { /** Emits an auth request id when an auth request has been approved. */ @@ -75,17 +75,6 @@ export abstract class AuthRequestServiceAbstraction { authReqPrivateKey: ArrayBuffer, userId: UserId, ): Promise; - /** - * Sets the `MasterKey` and `MasterKeyHash` from an auth request. Auth request must have a `MasterKey` and `MasterKeyHash`. - * @param authReqResponse The auth request. - * @param authReqPrivateKey The private key corresponding to the public key sent in the auth request. - * @param userId The ID of the user for whose account we will set the keys. - */ - abstract setKeysAfterDecryptingSharedMasterKeyAndHash( - authReqResponse: AuthRequestResponse, - authReqPrivateKey: ArrayBuffer, - userId: UserId, - ): Promise; /** * Decrypts a `UserKey` from a public key encrypted `UserKey`. * @param pubKeyEncryptedUserKey The public key encrypted `UserKey`. @@ -96,18 +85,6 @@ export abstract class AuthRequestServiceAbstraction { pubKeyEncryptedUserKey: string, privateKey: ArrayBuffer, ): Promise; - /** - * Decrypts a `MasterKey` and `MasterKeyHash` from a public key encrypted `MasterKey` and `MasterKeyHash`. - * @param pubKeyEncryptedMasterKey The public key encrypted `MasterKey`. - * @param pubKeyEncryptedMasterKeyHash The public key encrypted `MasterKeyHash`. - * @param privateKey The private key corresponding to the public key used to encrypt the `MasterKey` and `MasterKeyHash`. - * @returns The decrypted `MasterKey` and `MasterKeyHash`. - */ - abstract decryptPubKeyEncryptedMasterKeyAndHash( - pubKeyEncryptedMasterKey: string, - pubKeyEncryptedMasterKeyHash: string, - privateKey: ArrayBuffer, - ): Promise<{ masterKey: MasterKey; masterKeyHash: string }>; /** * Handles incoming auth request push server notifications. diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 3dbce6500a8..2137cbad15e 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -411,24 +411,6 @@ describe("SsoLoginStrategy", () => { ); }); - it("sets the user key using master key and hash from approved admin request if exists", async () => { - apiService.postIdentityToken.mockResolvedValue(tokenResponse); - keyService.hasUserKey.mockResolvedValue(true); - const adminAuthResponse = { - id: "1", - publicKey: "PRIVATE" as any, - key: "KEY" as any, - masterPasswordHash: "HASH" as any, - requestApproved: true, - }; - apiService.getAuthRequest.mockResolvedValue(adminAuthResponse as AuthRequestResponse); - - await ssoLoginStrategy.logIn(credentials); - - expect(authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash).toHaveBeenCalled(); - expect(deviceTrustService.decryptUserKeyWithDeviceKey).not.toHaveBeenCalled(); - }); - it("sets the user key from approved admin request if exists", async () => { apiService.postIdentityToken.mockResolvedValue(tokenResponse); keyService.hasUserKey.mockResolvedValue(true); @@ -470,9 +452,6 @@ describe("SsoLoginStrategy", () => { await ssoLoginStrategy.logIn(credentials); expect(authRequestService.clearAdminAuthRequest).toHaveBeenCalled(); - expect( - authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash, - ).not.toHaveBeenCalled(); expect(authRequestService.setUserKeyAfterDecryptingSharedUserKey).not.toHaveBeenCalled(); expect(deviceTrustService.trustDeviceIfRequired).not.toHaveBeenCalled(); }); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index 33802765aca..10590994297 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -239,23 +239,11 @@ export class SsoLoginStrategy extends LoginStrategy { } if (adminAuthReqResponse?.requestApproved) { - // if masterPasswordHash has a value, we will always receive authReqResponse.key - // as authRequestPublicKey(masterKey) + authRequestPublicKey(masterPasswordHash) - if (adminAuthReqResponse.masterPasswordHash) { - await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( - adminAuthReqResponse, - adminAuthReqStorable.privateKey, - userId, - ); - } else { - // if masterPasswordHash is null, we will always receive authReqResponse.key - // as authRequestPublicKey(userKey) - await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( - adminAuthReqResponse, - adminAuthReqStorable.privateKey, - userId, - ); - } + await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( + adminAuthReqResponse, + adminAuthReqStorable.privateKey, + userId, + ); if (await this.keyService.hasUserKey(userId)) { // Now that we have a decrypted user key in memory, we can check if we 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 8cb0cc279ae..a3f79f45ad5 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 @@ -13,7 +13,7 @@ import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.ser import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; +import { UserKey } from "@bitwarden/common/types/key"; import { newGuid } from "@bitwarden/guid"; import { KeyService } from "@bitwarden/key-management"; @@ -154,60 +154,6 @@ describe("AuthRequestService", () => { }); }); - describe("setKeysAfterDecryptingSharedMasterKeyAndHash", () => { - it("decrypts and sets master key and hash and user key when given valid auth request response and private key", async () => { - // Arrange - const mockAuthReqResponse = { - key: "authReqPublicKeyEncryptedMasterKey", - masterPasswordHash: "authReqPublicKeyEncryptedMasterKeyHash", - } as AuthRequestResponse; - - const mockDecryptedMasterKey = {} as MasterKey; - const mockDecryptedMasterKeyHash = "mockDecryptedMasterKeyHash"; - const mockDecryptedUserKey = {} as UserKey; - - jest.spyOn(sut, "decryptPubKeyEncryptedMasterKeyAndHash").mockResolvedValueOnce({ - masterKey: mockDecryptedMasterKey, - masterKeyHash: mockDecryptedMasterKeyHash, - }); - - masterPasswordService.masterKeySubject.next(undefined); - masterPasswordService.masterKeyHashSubject.next(undefined); - masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue( - mockDecryptedUserKey, - ); - keyService.setUserKey.mockResolvedValueOnce(undefined); - - // Act - await sut.setKeysAfterDecryptingSharedMasterKeyAndHash( - mockAuthReqResponse, - mockPrivateKey, - mockUserId, - ); - - // Assert - expect(sut.decryptPubKeyEncryptedMasterKeyAndHash).toBeCalledWith( - mockAuthReqResponse.key, - mockAuthReqResponse.masterPasswordHash, - mockPrivateKey, - ); - expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith( - mockDecryptedMasterKey, - mockUserId, - ); - expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith( - mockDecryptedMasterKeyHash, - mockUserId, - ); - expect(masterPasswordService.mock.decryptUserKeyWithMasterKey).toHaveBeenCalledWith( - mockDecryptedMasterKey, - mockUserId, - undefined, - ); - expect(keyService.setUserKey).toHaveBeenCalledWith(mockDecryptedUserKey, mockUserId); - }); - }); - describe("decryptAuthReqPubKeyEncryptedUserKey", () => { it("returns a decrypted user key when given valid public key encrypted user key and an auth req private key", async () => { // Arrange 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 ba4b9eaf174..f1ff8416d11 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 @@ -16,14 +16,13 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { AUTH_REQUEST_DISK_LOCAL, StateProvider, UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; +import { UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; import { AuthRequestApiServiceAbstraction } from "../../abstractions/auth-request-api.service"; @@ -163,27 +162,6 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { await this.keyService.setUserKey(userKey, userId); } - async setKeysAfterDecryptingSharedMasterKeyAndHash( - authReqResponse: AuthRequestResponse, - authReqPrivateKey: Uint8Array, - userId: UserId, - ) { - const { masterKey, masterKeyHash } = await this.decryptPubKeyEncryptedMasterKeyAndHash( - authReqResponse.key, - authReqResponse.masterPasswordHash, - authReqPrivateKey, - ); - - // Decrypt and set user key in state - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId); - - // Set masterKey + masterKeyHash in state after decryption (in case decryption fails) - await this.masterPasswordService.setMasterKey(masterKey, userId); - await this.masterPasswordService.setMasterKeyHash(masterKeyHash, userId); - - await this.keyService.setUserKey(userKey, userId); - } - // Decryption helpers async decryptPubKeyEncryptedUserKey( pubKeyEncryptedUserKey: string, @@ -197,30 +175,6 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { return decryptedUserKey as UserKey; } - async decryptPubKeyEncryptedMasterKeyAndHash( - pubKeyEncryptedMasterKey: string, - pubKeyEncryptedMasterKeyHash: string, - privateKey: Uint8Array, - ): Promise<{ masterKey: MasterKey; masterKeyHash: string }> { - const decryptedMasterKeyArrayBuffer = await this.encryptService.rsaDecrypt( - new EncString(pubKeyEncryptedMasterKey), - privateKey, - ); - - const decryptedMasterKeyHashArrayBuffer = await this.encryptService.rsaDecrypt( - new EncString(pubKeyEncryptedMasterKeyHash), - privateKey, - ); - - const masterKey = new SymmetricCryptoKey(decryptedMasterKeyArrayBuffer) as MasterKey; - const masterKeyHash = Utils.fromBufferToUtf8(decryptedMasterKeyHashArrayBuffer); - - return { - masterKey, - masterKeyHash, - }; - } - sendAuthRequestPushNotification(notification: AuthRequestPushNotification): void { if (notification.id != null) { this.authRequestPushNotificationSubject.next(notification.id); diff --git a/libs/common/src/auth/models/response/auth-request.response.ts b/libs/common/src/auth/models/response/auth-request.response.ts index 94c65000919..8d02e161e68 100644 --- a/libs/common/src/auth/models/response/auth-request.response.ts +++ b/libs/common/src/auth/models/response/auth-request.response.ts @@ -11,8 +11,7 @@ export class AuthRequestResponse extends BaseResponse { requestDeviceIdentifier: string; requestIpAddress: string; requestCountryName: string; - key: string; // could be either an encrypted MasterKey or an encrypted UserKey - masterPasswordHash: string; // if hash is present, the `key` above is an encrypted MasterKey (else `key` is an encrypted UserKey) + key: string; // Auth-request public-key encrypted user-key. Note: No sender authenticity provided! creationDate: string; requestApproved?: boolean; responseDate?: string; @@ -30,7 +29,6 @@ export class AuthRequestResponse extends BaseResponse { this.requestIpAddress = this.getResponseProperty("RequestIpAddress"); this.requestCountryName = this.getResponseProperty("RequestCountryName"); this.key = this.getResponseProperty("Key"); - this.masterPasswordHash = this.getResponseProperty("MasterPasswordHash"); this.creationDate = this.getResponseProperty("CreationDate"); this.requestApproved = this.getResponseProperty("RequestApproved"); this.responseDate = this.getResponseProperty("ResponseDate"); 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 25e5f949b40..d6008aca819 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -158,13 +158,6 @@ export abstract class EncryptService { decapsulationKey: Uint8Array, ): Promise; - /** - * @deprecated Use @see {@link decapsulateKeyUnsigned} instead - * @param data - The ciphertext to decrypt - * @param privateKey - The privateKey to decrypt with - */ - abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; - /** * Generates a base64-encoded hash of the given value * @param value The value to hash 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 a5da0c82382..db708a5bdb4 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 @@ -246,24 +246,4 @@ export class EncryptServiceImplementation implements EncryptService { ); return new SymmetricCryptoKey(keyBytes); } - - async rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise { - if (data == null) { - throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption."); - } - - switch (data.encryptionType) { - case EncryptionType.Rsa2048_OaepSha1_B64: - case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: - 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, "sha1"); - } }