mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 07:23:45 +00:00
Remove deprecated master key login with device flow
This commit is contained in:
@@ -679,27 +679,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
|
||||
privateKey: ArrayBuffer,
|
||||
userId: UserId,
|
||||
): Promise<void> {
|
||||
/**
|
||||
* [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)
|
||||
|
||||
@@ -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<void>;
|
||||
/**
|
||||
* 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<void>;
|
||||
/**
|
||||
* 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<UserKey>;
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -158,13 +158,6 @@ export abstract class EncryptService {
|
||||
decapsulationKey: Uint8Array,
|
||||
): Promise<SymmetricCryptoKey>;
|
||||
|
||||
/**
|
||||
* @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<Uint8Array>;
|
||||
|
||||
/**
|
||||
* Generates a base64-encoded hash of the given value
|
||||
* @param value The value to hash
|
||||
|
||||
@@ -246,24 +246,4 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
);
|
||||
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.");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user