mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
[PM-5499] Create Auth Request Service (#8056)
* create auth request service * copy methods from auth crypto service * register new auth request service * remove refs to auth request crypto service * remove auth request crypto service * remove passwordless login method from login strategy service * add docs to auth request service
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
export abstract class AuthRequestServiceAbstraction {
|
||||
/**
|
||||
* Approve or deny an auth request.
|
||||
* @param approve True to approve, false to deny.
|
||||
* @param authRequest The auth request to approve or deny, must have an id and key.
|
||||
* @returns The updated auth request, the `requestApproved` field will be true if
|
||||
* approval was successful.
|
||||
* @throws If the auth request is missing an id or key.
|
||||
*/
|
||||
abstract approveOrDenyAuthRequest: (
|
||||
approve: boolean,
|
||||
authRequest: AuthRequestResponse,
|
||||
) => Promise<AuthRequestResponse>;
|
||||
/**
|
||||
* Sets the `UserKey` from an auth request. Auth request must have a `UserKey`.
|
||||
* @param authReqResponse The auth request.
|
||||
* @param authReqPrivateKey The private key corresponding to the public key sent in the auth request.
|
||||
*/
|
||||
abstract setUserKeyAfterDecryptingSharedUserKey: (
|
||||
authReqResponse: AuthRequestResponse,
|
||||
authReqPrivateKey: ArrayBuffer,
|
||||
) => 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.
|
||||
*/
|
||||
abstract setKeysAfterDecryptingSharedMasterKeyAndHash: (
|
||||
authReqResponse: AuthRequestResponse,
|
||||
authReqPrivateKey: ArrayBuffer,
|
||||
) => Promise<void>;
|
||||
/**
|
||||
* Decrypts a `UserKey` from a public key encrypted `UserKey`.
|
||||
* @param pubKeyEncryptedUserKey The public key encrypted `UserKey`.
|
||||
* @param privateKey The private key corresponding to the public key used to encrypt the `UserKey`.
|
||||
* @returns The decrypted `UserKey`.
|
||||
*/
|
||||
abstract decryptPubKeyEncryptedUserKey: (
|
||||
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 }>;
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./pin-crypto.service.abstraction";
|
||||
export * from "./login-strategy.service";
|
||||
export * from "./auth-request.service.abstraction";
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Observable } from "rxjs";
|
||||
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
|
||||
import { MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
@@ -39,10 +38,5 @@ export abstract class LoginStrategyServiceAbstraction {
|
||||
authingWithPassword: () => boolean;
|
||||
authingWithPasswordless: () => boolean;
|
||||
authResponsePushNotification: (notification: AuthRequestPushNotification) => Promise<any>;
|
||||
passwordlessLogin: (
|
||||
id: string,
|
||||
key: string,
|
||||
requestApproved: boolean,
|
||||
) => Promise<AuthRequestResponse>;
|
||||
getPushNotificationObs$: () => Observable<any>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
@@ -20,6 +19,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { AuthRequestServiceAbstraction } from "../abstractions";
|
||||
import { SsoLoginCredentials } from "../models/domain/login-credentials";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
@@ -40,7 +40,7 @@ describe("SsoLoginStrategy", () => {
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||
let authRequestCryptoService: MockProxy<AuthRequestCryptoServiceAbstraction>;
|
||||
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
|
||||
let ssoLoginStrategy: SsoLoginStrategy;
|
||||
@@ -66,7 +66,7 @@ describe("SsoLoginStrategy", () => {
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||
authRequestCryptoService = mock<AuthRequestCryptoServiceAbstraction>();
|
||||
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||
i18nService = mock<I18nService>();
|
||||
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
@@ -85,7 +85,7 @@ describe("SsoLoginStrategy", () => {
|
||||
twoFactorService,
|
||||
keyConnectorService,
|
||||
deviceTrustCryptoService,
|
||||
authRequestCryptoService,
|
||||
authRequestService,
|
||||
i18nService,
|
||||
);
|
||||
credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
@@ -18,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
|
||||
import { AuthRequestServiceAbstraction } from "../abstractions";
|
||||
import { SsoLoginCredentials } from "../models/domain/login-credentials";
|
||||
|
||||
import { LoginStrategy } from "./login.strategy";
|
||||
@@ -44,7 +44,7 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
twoFactorService: TwoFactorService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
private authReqCryptoService: AuthRequestCryptoServiceAbstraction,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
) {
|
||||
super(
|
||||
@@ -199,14 +199,14 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
// if masterPasswordHash has a value, we will always receive authReqResponse.key
|
||||
// as authRequestPublicKey(masterKey) + authRequestPublicKey(masterPasswordHash)
|
||||
if (adminAuthReqResponse.masterPasswordHash) {
|
||||
await this.authReqCryptoService.setKeysAfterDecryptingSharedMasterKeyAndHash(
|
||||
await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash(
|
||||
adminAuthReqResponse,
|
||||
adminAuthReqStorable.privateKey,
|
||||
);
|
||||
} else {
|
||||
// if masterPasswordHash is null, we will always receive authReqResponse.key
|
||||
// as authRequestPublicKey(userKey)
|
||||
await this.authReqCryptoService.setUserKeyAfterDecryptingSharedUserKey(
|
||||
await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey(
|
||||
adminAuthReqResponse,
|
||||
adminAuthReqStorable.privateKey,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.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 { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { AuthRequestService } from "./auth-request.service";
|
||||
|
||||
describe("AuthRequestService", () => {
|
||||
let sut: AuthRequestService;
|
||||
|
||||
const appIdService = mock<AppIdService>();
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const apiService = mock<ApiService>();
|
||||
const stateService = mock<StateService>();
|
||||
|
||||
let mockPrivateKey: Uint8Array;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
sut = new AuthRequestService(appIdService, cryptoService, apiService, stateService);
|
||||
|
||||
mockPrivateKey = new Uint8Array(64);
|
||||
});
|
||||
|
||||
describe("approveOrDenyAuthRequest", () => {
|
||||
beforeEach(() => {
|
||||
cryptoService.rsaEncrypt.mockResolvedValue({
|
||||
encryptedString: "ENCRYPTED_STRING",
|
||||
} as EncString);
|
||||
appIdService.getAppId.mockResolvedValue("APP_ID");
|
||||
});
|
||||
it("should throw if auth request is missing id or key", async () => {
|
||||
const authRequestNoId = new AuthRequestResponse({ id: "", key: "KEY" });
|
||||
const authRequestNoKey = new AuthRequestResponse({ id: "123", key: "" });
|
||||
|
||||
await expect(sut.approveOrDenyAuthRequest(true, authRequestNoId)).rejects.toThrow(
|
||||
"Auth request has no id",
|
||||
);
|
||||
await expect(sut.approveOrDenyAuthRequest(true, authRequestNoKey)).rejects.toThrow(
|
||||
"Auth request has no public key",
|
||||
);
|
||||
});
|
||||
|
||||
it("should use the master key and hash if they exist", async () => {
|
||||
cryptoService.getMasterKey.mockResolvedValueOnce({ encKey: new Uint8Array(64) } as MasterKey);
|
||||
stateService.getKeyHash.mockResolvedValueOnce("KEY_HASH");
|
||||
|
||||
await sut.approveOrDenyAuthRequest(true, new AuthRequestResponse({ id: "123", key: "KEY" }));
|
||||
|
||||
expect(cryptoService.rsaEncrypt).toHaveBeenCalledWith(new Uint8Array(64), expect.anything());
|
||||
});
|
||||
|
||||
it("should use the user key if the master key and hash do not exist", async () => {
|
||||
cryptoService.getUserKey.mockResolvedValueOnce({ key: new Uint8Array(64) } as UserKey);
|
||||
|
||||
await sut.approveOrDenyAuthRequest(true, new AuthRequestResponse({ id: "123", key: "KEY" }));
|
||||
|
||||
expect(cryptoService.rsaEncrypt).toHaveBeenCalledWith(new Uint8Array(64), expect.anything());
|
||||
});
|
||||
});
|
||||
describe("setUserKeyAfterDecryptingSharedUserKey", () => {
|
||||
it("decrypts and sets user key when given valid auth request response and private key", async () => {
|
||||
// Arrange
|
||||
const mockAuthReqResponse = {
|
||||
key: "authReqPublicKeyEncryptedUserKey",
|
||||
} as AuthRequestResponse;
|
||||
|
||||
const mockDecryptedUserKey = {} as UserKey;
|
||||
jest.spyOn(sut, "decryptPubKeyEncryptedUserKey").mockResolvedValueOnce(mockDecryptedUserKey);
|
||||
|
||||
cryptoService.setUserKey.mockResolvedValueOnce(undefined);
|
||||
|
||||
// Act
|
||||
await sut.setUserKeyAfterDecryptingSharedUserKey(mockAuthReqResponse, mockPrivateKey);
|
||||
|
||||
// Assert
|
||||
expect(sut.decryptPubKeyEncryptedUserKey).toBeCalledWith(
|
||||
mockAuthReqResponse.key,
|
||||
mockPrivateKey,
|
||||
);
|
||||
expect(cryptoService.setUserKey).toBeCalledWith(mockDecryptedUserKey);
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
cryptoService.setMasterKey.mockResolvedValueOnce(undefined);
|
||||
cryptoService.setMasterKeyHash.mockResolvedValueOnce(undefined);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValueOnce(mockDecryptedUserKey);
|
||||
cryptoService.setUserKey.mockResolvedValueOnce(undefined);
|
||||
|
||||
// Act
|
||||
await sut.setKeysAfterDecryptingSharedMasterKeyAndHash(mockAuthReqResponse, mockPrivateKey);
|
||||
|
||||
// Assert
|
||||
expect(sut.decryptPubKeyEncryptedMasterKeyAndHash).toBeCalledWith(
|
||||
mockAuthReqResponse.key,
|
||||
mockAuthReqResponse.masterPasswordHash,
|
||||
mockPrivateKey,
|
||||
);
|
||||
expect(cryptoService.setMasterKey).toBeCalledWith(mockDecryptedMasterKey);
|
||||
expect(cryptoService.setMasterKeyHash).toBeCalledWith(mockDecryptedMasterKeyHash);
|
||||
expect(cryptoService.decryptUserKeyWithMasterKey).toBeCalledWith(mockDecryptedMasterKey);
|
||||
expect(cryptoService.setUserKey).toBeCalledWith(mockDecryptedUserKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("decryptAuthReqPubKeyEncryptedUserKey", () => {
|
||||
it("returns a decrypted user key when given valid public key encrypted user key and an auth req private key", async () => {
|
||||
// Arrange
|
||||
const mockPubKeyEncryptedUserKey = "pubKeyEncryptedUserKey";
|
||||
const mockDecryptedUserKeyBytes = new Uint8Array(64);
|
||||
const mockDecryptedUserKey = new SymmetricCryptoKey(mockDecryptedUserKeyBytes) as UserKey;
|
||||
|
||||
cryptoService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedUserKeyBytes);
|
||||
|
||||
// Act
|
||||
const result = await sut.decryptPubKeyEncryptedUserKey(
|
||||
mockPubKeyEncryptedUserKey,
|
||||
mockPrivateKey,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(cryptoService.rsaDecrypt).toBeCalledWith(mockPubKeyEncryptedUserKey, mockPrivateKey);
|
||||
expect(result).toEqual(mockDecryptedUserKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("decryptAuthReqPubKeyEncryptedMasterKeyAndHash", () => {
|
||||
it("returns a decrypted master key and hash when given a valid public key encrypted master key, public key encrypted master key hash, and an auth req private key", async () => {
|
||||
// Arrange
|
||||
const mockPubKeyEncryptedMasterKey = "pubKeyEncryptedMasterKey";
|
||||
const mockPubKeyEncryptedMasterKeyHash = "pubKeyEncryptedMasterKeyHash";
|
||||
|
||||
const mockDecryptedMasterKeyBytes = new Uint8Array(64);
|
||||
const mockDecryptedMasterKey = new SymmetricCryptoKey(
|
||||
mockDecryptedMasterKeyBytes,
|
||||
) as MasterKey;
|
||||
const mockDecryptedMasterKeyHashBytes = new Uint8Array(64);
|
||||
const mockDecryptedMasterKeyHash = Utils.fromBufferToUtf8(mockDecryptedMasterKeyHashBytes);
|
||||
|
||||
cryptoService.rsaDecrypt
|
||||
.mockResolvedValueOnce(mockDecryptedMasterKeyBytes)
|
||||
.mockResolvedValueOnce(mockDecryptedMasterKeyHashBytes);
|
||||
|
||||
// Act
|
||||
const result = await sut.decryptPubKeyEncryptedMasterKeyAndHash(
|
||||
mockPubKeyEncryptedMasterKey,
|
||||
mockPubKeyEncryptedMasterKeyHash,
|
||||
mockPrivateKey,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(cryptoService.rsaDecrypt).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
mockPubKeyEncryptedMasterKey,
|
||||
mockPrivateKey,
|
||||
);
|
||||
expect(cryptoService.rsaDecrypt).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
mockPubKeyEncryptedMasterKeyHash,
|
||||
mockPrivateKey,
|
||||
);
|
||||
expect(result.masterKey).toEqual(mockDecryptedMasterKey);
|
||||
expect(result.masterKeyHash).toEqual(mockDecryptedMasterKeyHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { AuthRequestServiceAbstraction } from "../../abstractions/auth-request.service.abstraction";
|
||||
|
||||
export class AuthRequestService implements AuthRequestServiceAbstraction {
|
||||
constructor(
|
||||
private appIdService: AppIdService,
|
||||
private cryptoService: CryptoService,
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
) {}
|
||||
|
||||
async approveOrDenyAuthRequest(
|
||||
approve: boolean,
|
||||
authRequest: AuthRequestResponse,
|
||||
): Promise<AuthRequestResponse> {
|
||||
if (!authRequest.id) {
|
||||
throw new Error("Auth request has no id");
|
||||
}
|
||||
if (!authRequest.key) {
|
||||
throw new Error("Auth request has no public key");
|
||||
}
|
||||
const pubKey = Utils.fromB64ToArray(authRequest.key);
|
||||
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const masterKeyHash = await this.stateService.getKeyHash();
|
||||
let encryptedMasterKeyHash;
|
||||
let keyToEncrypt;
|
||||
|
||||
if (masterKey && masterKeyHash) {
|
||||
// Only encrypt the master password hash if masterKey exists as
|
||||
// we won't have a masterKeyHash without a masterKey
|
||||
encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromUtf8ToArray(masterKeyHash),
|
||||
pubKey,
|
||||
);
|
||||
keyToEncrypt = masterKey.encKey;
|
||||
} else {
|
||||
const userKey = await this.cryptoService.getUserKey();
|
||||
keyToEncrypt = userKey.key;
|
||||
}
|
||||
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey);
|
||||
|
||||
const response = new PasswordlessAuthRequest(
|
||||
encryptedKey.encryptedString,
|
||||
encryptedMasterKeyHash?.encryptedString,
|
||||
await this.appIdService.getAppId(),
|
||||
approve,
|
||||
);
|
||||
return await this.apiService.putAuthRequest(authRequest.id, response);
|
||||
}
|
||||
|
||||
async setUserKeyAfterDecryptingSharedUserKey(
|
||||
authReqResponse: AuthRequestResponse,
|
||||
authReqPrivateKey: Uint8Array,
|
||||
) {
|
||||
const userKey = await this.decryptPubKeyEncryptedUserKey(
|
||||
authReqResponse.key,
|
||||
authReqPrivateKey,
|
||||
);
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
}
|
||||
|
||||
async setKeysAfterDecryptingSharedMasterKeyAndHash(
|
||||
authReqResponse: AuthRequestResponse,
|
||||
authReqPrivateKey: Uint8Array,
|
||||
) {
|
||||
const { masterKey, masterKeyHash } = await this.decryptPubKeyEncryptedMasterKeyAndHash(
|
||||
authReqResponse.key,
|
||||
authReqResponse.masterPasswordHash,
|
||||
authReqPrivateKey,
|
||||
);
|
||||
|
||||
// Decrypt and set user key in state
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
|
||||
|
||||
// Set masterKey + masterKeyHash in state after decryption (in case decryption fails)
|
||||
await this.cryptoService.setMasterKey(masterKey);
|
||||
await this.cryptoService.setMasterKeyHash(masterKeyHash);
|
||||
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
}
|
||||
|
||||
// Decryption helpers
|
||||
async decryptPubKeyEncryptedUserKey(
|
||||
pubKeyEncryptedUserKey: string,
|
||||
privateKey: Uint8Array,
|
||||
): Promise<UserKey> {
|
||||
const decryptedUserKeyBytes = await this.cryptoService.rsaDecrypt(
|
||||
pubKeyEncryptedUserKey,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
return new SymmetricCryptoKey(decryptedUserKeyBytes) as UserKey;
|
||||
}
|
||||
|
||||
async decryptPubKeyEncryptedMasterKeyAndHash(
|
||||
pubKeyEncryptedMasterKey: string,
|
||||
pubKeyEncryptedMasterKeyHash: string,
|
||||
privateKey: Uint8Array,
|
||||
): Promise<{ masterKey: MasterKey; masterKeyHash: string }> {
|
||||
const decryptedMasterKeyArrayBuffer = await this.cryptoService.rsaDecrypt(
|
||||
pubKeyEncryptedMasterKey,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
const decryptedMasterKeyHashArrayBuffer = await this.cryptoService.rsaDecrypt(
|
||||
pubKeyEncryptedMasterKeyHash,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
const masterKey = new SymmetricCryptoKey(decryptedMasterKeyArrayBuffer) as MasterKey;
|
||||
const masterKeyHash = Utils.fromBufferToUtf8(decryptedMasterKeyHashArrayBuffer);
|
||||
|
||||
return {
|
||||
masterKey,
|
||||
masterKeyHash,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./pin-crypto/pin-crypto.service.implementation";
|
||||
export * from "./login-strategies/login-strategy.service";
|
||||
export * from "./auth-request/auth-request.service";
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Observable, Subject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
@@ -11,8 +10,6 @@ import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
|
||||
@@ -26,11 +23,10 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { KdfType } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
import { MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "../../abstractions";
|
||||
import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction } from "../../abstractions";
|
||||
import { AuthRequestLoginStrategy } from "../../login-strategies/auth-request-login.strategy";
|
||||
import { PasswordLoginStrategy } from "../../login-strategies/password-login.strategy";
|
||||
import { SsoLoginStrategy } from "../../login-strategies/sso-login.strategy";
|
||||
@@ -110,7 +106,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||
protected policyService: PolicyService,
|
||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
protected authReqCryptoService: AuthRequestCryptoServiceAbstraction,
|
||||
protected authRequestService: AuthRequestServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async logIn(
|
||||
@@ -160,7 +156,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
this.twoFactorService,
|
||||
this.keyConnectorService,
|
||||
this.deviceTrustCryptoService,
|
||||
this.authReqCryptoService,
|
||||
this.authRequestService,
|
||||
this.i18nService,
|
||||
);
|
||||
break;
|
||||
@@ -290,45 +286,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
return this.pushNotificationSubject.asObservable();
|
||||
}
|
||||
|
||||
async passwordlessLogin(
|
||||
id: string,
|
||||
key: string,
|
||||
requestApproved: boolean,
|
||||
): Promise<AuthRequestResponse> {
|
||||
const pubKey = Utils.fromB64ToArray(key);
|
||||
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
let keyToEncrypt;
|
||||
let encryptedMasterKeyHash = null;
|
||||
|
||||
if (masterKey) {
|
||||
keyToEncrypt = masterKey.encKey;
|
||||
|
||||
// Only encrypt the master password hash if masterKey exists as
|
||||
// we won't have a masterKeyHash without a masterKey
|
||||
const masterKeyHash = await this.stateService.getKeyHash();
|
||||
if (masterKeyHash != null) {
|
||||
encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromUtf8ToArray(masterKeyHash),
|
||||
pubKey,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const userKey = await this.cryptoService.getUserKey();
|
||||
keyToEncrypt = userKey.key;
|
||||
}
|
||||
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey);
|
||||
|
||||
const request = new PasswordlessAuthRequest(
|
||||
encryptedKey.encryptedString,
|
||||
encryptedMasterKeyHash?.encryptedString,
|
||||
await this.appIdService.getAppId(),
|
||||
requestApproved,
|
||||
);
|
||||
return await this.apiService.putAuthRequest(id, request);
|
||||
}
|
||||
|
||||
private saveState(
|
||||
strategy:
|
||||
| UserApiLoginStrategy
|
||||
|
||||
Reference in New Issue
Block a user