Browse Source

Remove deprecated master key login with device flow

km/beeep/drop-masterkey-auth-request
Bernd Schoolmann 6 days ago
parent
commit
c56b50d476
No known key found for this signature in database
  1. 15
      libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
  2. 25
      libs/auth/src/common/abstractions/auth-request.service.abstraction.ts
  3. 21
      libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts
  4. 12
      libs/auth/src/common/login-strategies/sso-login.strategy.ts
  5. 56
      libs/auth/src/common/services/auth-request/auth-request.service.spec.ts
  6. 48
      libs/auth/src/common/services/auth-request/auth-request.service.ts
  7. 4
      libs/common/src/auth/models/response/auth-request.response.ts
  8. 7
      libs/common/src/key-management/crypto/abstractions/encrypt.service.ts
  9. 20
      libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts

15
libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts

@ -679,27 +679,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { @@ -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,
);
}
// [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)

25
libs/auth/src/common/abstractions/auth-request.service.abstraction.ts

@ -4,7 +4,7 @@ import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/a @@ -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 { @@ -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 { @@ -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.

21
libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts

@ -411,24 +411,6 @@ describe("SsoLoginStrategy", () => { @@ -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", () => { @@ -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();
});

12
libs/auth/src/common/login-strategies/sso-login.strategy.ts

@ -239,23 +239,11 @@ export class SsoLoginStrategy extends LoginStrategy { @@ -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,
);
}
if (await this.keyService.hasUserKey(userId)) {
// Now that we have a decrypted user key in memory, we can check if we

56
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 @@ -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", () => { @@ -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

48
libs/auth/src/common/services/auth-request/auth-request.service.ts

@ -16,14 +16,13 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response"; @@ -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 { @@ -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 { @@ -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);

4
libs/common/src/auth/models/response/auth-request.response.ts

@ -11,8 +11,7 @@ export class AuthRequestResponse extends BaseResponse { @@ -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 { @@ -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");

7
libs/common/src/key-management/crypto/abstractions/encrypt.service.ts

@ -158,13 +158,6 @@ export abstract class EncryptService { @@ -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

20
libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts

@ -246,24 +246,4 @@ export class EncryptServiceImplementation implements EncryptService { @@ -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");
}
}

Loading…
Cancel
Save