1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-10 14:43:34 +01:00
bitwarden-browser/src/services/nativeMessaging.service.ts

189 lines
6.1 KiB
TypeScript
Raw Normal View History

2021-12-20 15:47:17 +01:00
import { Injectable } from "@angular/core";
import { ipcRenderer } from "electron";
import Swal from "sweetalert2";
2020-10-05 20:05:48 +02:00
2021-12-20 15:47:17 +01:00
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
2021-12-20 15:47:17 +01:00
import { Utils } from "jslib-common/misc/utils";
[Account Switching] [Feature] Add the ability to maintain state for up to 5 accounts at once (#1079) * [refactor] Remove references to deprecated services * [feature] Implement account switching * [bug] Fix state handling for authentication dependent system menu items * [bug] Enable the account switcher to fucntion properly when switching to a locked accounts * [feature] Enable locking any account from the menu * [bug] Ensure the avatar instance used in the account switcher updates on account change * [style] Fix lint complaints * [bug] Ensure the logout command callback can handle any user in state * [style] Fix lint complaints * rollup * [style] Fix lint complaints * [bug] Don't clean up state until everything else is done on logout * [bug] Navigate to vault on a succesful account switch * [bug] Init the state service on start * [feature] Limit account switching to 5 account maximum * [bug] Resolve app lock state with 5 logged out accounts * [chore] Update account refrences to match recent jslib restructuring * [bug] Add missing awaits * [bug] Update app menu on logout * [bug] Hide the switcher if there are no authed accounts * [bug] Move authenticationStatus display information out of jslib * [bug] Remove unused active style from scss * [refactor] Rewrite the menu bar * [style] Fix lint complaints * [bug] Clean state of loggout out user after redirect * [bug] Redirect on logout if not explicity provided a userId that isn't active * [bug] Relocated several settings items to persistant storage * [bug] Correct account switcher styles on all themes * [chore] Include state migration service in services * [bug] Swap to next account on logout * [bug] Correct DI service * [bug] fix loginGuard deps in services.module * [chore] update jslib * [bug] Remove badly merged scss * [chore] update jslib * [review] Code review cleanup * [review] Code review cleanup Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-15 23:32:00 +01:00
import { EncString } from "jslib-common/models/domain/encString";
2021-12-20 15:47:17 +01:00
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
[Account Switching] [Feature] Add the ability to maintain state for up to 5 accounts at once (#1079) * [refactor] Remove references to deprecated services * [feature] Implement account switching * [bug] Fix state handling for authentication dependent system menu items * [bug] Enable the account switcher to fucntion properly when switching to a locked accounts * [feature] Enable locking any account from the menu * [bug] Ensure the avatar instance used in the account switcher updates on account change * [style] Fix lint complaints * [bug] Ensure the logout command callback can handle any user in state * [style] Fix lint complaints * rollup * [style] Fix lint complaints * [bug] Don't clean up state until everything else is done on logout * [bug] Navigate to vault on a succesful account switch * [bug] Init the state service on start * [feature] Limit account switching to 5 account maximum * [bug] Resolve app lock state with 5 logged out accounts * [chore] Update account refrences to match recent jslib restructuring * [bug] Add missing awaits * [bug] Update app menu on logout * [bug] Hide the switcher if there are no authed accounts * [bug] Move authenticationStatus display information out of jslib * [bug] Remove unused active style from scss * [refactor] Rewrite the menu bar * [style] Fix lint complaints * [bug] Clean state of loggout out user after redirect * [bug] Redirect on logout if not explicity provided a userId that isn't active * [bug] Relocated several settings items to persistant storage * [bug] Correct account switcher styles on all themes * [chore] Include state migration service in services * [bug] Swap to next account on logout * [bug] Correct DI service * [bug] fix loginGuard deps in services.module * [chore] update jslib * [bug] Remove badly merged scss * [chore] update jslib * [review] Code review cleanup * [review] Code review cleanup Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-15 23:32:00 +01:00
2021-12-20 15:47:17 +01:00
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
const MessageValidTimeout = 10 * 1000;
2021-12-20 15:47:17 +01:00
const EncryptionAlgorithm = "sha1";
type Message = {
command: string;
userId?: string;
timestamp?: number;
publicKey?: string;
};
type OuterMessage = {
message: Message | EncString;
appId: string;
};
2021-12-06 12:03:02 +01:00
@Injectable()
export class NativeMessagingService {
2021-12-20 15:47:17 +01:00
private sharedSecrets = new Map<string, SymmetricCryptoKey>();
constructor(
private cryptoFunctionService: CryptoFunctionService,
private cryptoService: CryptoService,
private platformUtilService: PlatformUtilsService,
private logService: LogService,
private i18nService: I18nService,
private messagingService: MessagingService,
private stateService: StateService
) {}
init() {
2021-12-20 15:47:17 +01:00
ipcRenderer.on("nativeMessaging", async (_event: any, message: any) => {
this.messageHandler(message);
});
}
private async messageHandler(msg: OuterMessage) {
2021-12-20 15:47:17 +01:00
const appId = msg.appId;
const rawMessage = msg.message;
// Request to setup secure encryption
if ("command" in rawMessage && rawMessage.command === "setupEncryption") {
2021-12-20 15:47:17 +01:00
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
// Valudate the UserId to ensure we are logged into the same account.
const userIds = Object.keys(this.stateService.accounts.getValue());
if (!userIds.includes(rawMessage.userId)) {
2021-12-20 15:47:17 +01:00
ipcRenderer.send("nativeMessagingReply", { command: "wrongUserId", appId: appId });
return;
}
if (await this.stateService.getEnableBrowserIntegrationFingerprint()) {
ipcRenderer.send("nativeMessagingReply", { command: "verifyFingerprint", appId: appId });
const fingerprint = (
await this.cryptoService.getFingerprint(
await this.stateService.getUserId(),
remotePublicKey
)
).join(" ");
this.messagingService.send("setFocus");
// Await confirmation that fingerprint is correct
const submitted = await Swal.fire({
titleText: this.i18nService.t("verifyBrowserTitle"),
html: `${this.i18nService.t("verifyBrowserDesc")}<br><br><strong>${fingerprint}</strong>`,
showCancelButton: true,
cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: true,
confirmButtonText: this.i18nService.t("approve"),
allowOutsideClick: false,
});
2021-12-20 15:47:17 +01:00
if (submitted.value !== true) {
return;
2020-10-16 17:09:17 +02:00
}
2021-12-20 15:47:17 +01:00
}
2020-10-05 20:05:48 +02:00
2021-12-20 15:47:17 +01:00
this.secureCommunication(remotePublicKey, appId);
return;
}
2020-10-19 12:21:07 +02:00
if (this.sharedSecrets.get(appId) == null) {
ipcRenderer.send("nativeMessagingReply", { command: "invalidateEncryption", appId: appId });
return;
}
const message: Message = JSON.parse(
await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecrets.get(appId))
2021-12-20 15:47:17 +01:00
);
2021-12-20 15:47:17 +01:00
// Shared secret is invalidated, force re-authentication
if (message == null) {
ipcRenderer.send("nativeMessagingReply", { command: "invalidateEncryption", appId: appId });
return;
}
2021-12-20 15:47:17 +01:00
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
this.logService.error("NativeMessage is to old, ignoring.");
return;
}
2021-12-20 15:47:17 +01:00
switch (message.command) {
case "biometricUnlock":
if (!this.platformUtilService.supportsBiometric()) {
return this.send({ command: "biometricUnlock", response: "not supported" }, appId);
}
2020-10-19 12:21:07 +02:00
if (!(await this.stateService.getBiometricUnlock({ userId: message.userId }))) {
2021-12-20 15:47:17 +01:00
this.send({ command: "biometricUnlock", response: "not enabled" }, appId);
2021-12-20 15:47:17 +01:00
return await Swal.fire({
title: this.i18nService.t("biometricsNotEnabledTitle"),
text: this.i18nService.t("biometricsNotEnabledDesc"),
showCancelButton: true,
cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: false,
});
}
2020-10-16 17:09:17 +02:00
const key = await this.cryptoService.getKeyFromStorage(
KeySuffixOptions.Biometric,
message.userId
);
2021-12-20 15:47:17 +01:00
if (key != null) {
this.send(
{ command: "biometricUnlock", response: "unlocked", keyB64: key.keyB64 },
appId
);
2021-12-20 15:47:17 +01:00
} else {
this.send({ command: "biometricUnlock", response: "canceled" }, appId);
}
2020-10-16 17:09:17 +02:00
2021-12-20 15:47:17 +01:00
break;
default:
this.logService.error("NativeMessage, got unknown command.");
2020-10-16 17:09:17 +02:00
}
2021-12-20 15:47:17 +01:00
}
private async send(message: any, appId: string) {
message.timestamp = Date.now();
const encrypted = await this.cryptoService.encrypt(
JSON.stringify(message),
this.sharedSecrets.get(appId)
);
ipcRenderer.send("nativeMessagingReply", { appId: appId, message: encrypted });
}
private async secureCommunication(remotePublicKey: ArrayBuffer, appId: string) {
const secret = await this.cryptoFunctionService.randomBytes(64);
this.sharedSecrets.set(appId, new SymmetricCryptoKey(secret));
const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt(
secret,
remotePublicKey,
EncryptionAlgorithm
);
ipcRenderer.send("nativeMessagingReply", {
appId: appId,
command: "setupEncryption",
sharedSecret: Utils.fromBufferToB64(encryptedSecret),
});
}
}