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";
|
|
|
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
2020-11-30 14:58:52 +01:00
|
|
|
|
2021-12-20 15:47:17 +01:00
|
|
|
import { Utils } from "jslib-common/misc/utils";
|
2021-12-15 23:32:00 +01:00
|
|
|
|
2021-12-20 15:47:17 +01:00
|
|
|
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
2021-12-15 23:32:00 +01:00
|
|
|
|
2021-12-20 15:47:17 +01:00
|
|
|
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
|
2020-10-12 21:34:41 +02:00
|
|
|
|
|
|
|
const MessageValidTimeout = 10 * 1000;
|
2021-12-20 15:47:17 +01:00
|
|
|
const EncryptionAlgorithm = "sha1";
|
2020-10-12 21:34:41 +02:00
|
|
|
|
2021-12-06 12:03:02 +01:00
|
|
|
@Injectable()
|
2020-10-05 15:11:37 +02:00
|
|
|
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 vaultTimeoutService: VaultTimeoutService,
|
|
|
|
private stateService: StateService
|
|
|
|
) {
|
|
|
|
ipcRenderer.on("nativeMessaging", async (_event: any, message: any) => {
|
|
|
|
this.messageHandler(message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private async messageHandler(msg: any) {
|
|
|
|
const appId = msg.appId;
|
|
|
|
const rawMessage = msg.message;
|
|
|
|
|
|
|
|
// Request to setup secure encryption
|
|
|
|
if (rawMessage.command === "setupEncryption") {
|
|
|
|
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
|
|
|
|
|
|
|
|
// Valudate the UserId to ensure we are logged into the same account.
|
|
|
|
if (rawMessage.userId !== (await this.stateService.getUserId())) {
|
|
|
|
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,
|
2020-10-05 15:11:37 +02:00
|
|
|
});
|
2020-10-05 19:48:51 +02:00
|
|
|
|
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
|
|
|
|
2021-12-20 15:47:17 +01:00
|
|
|
const message = JSON.parse(
|
|
|
|
await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecrets.get(appId))
|
|
|
|
);
|
2020-10-23 14:40:31 +02: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;
|
|
|
|
}
|
2020-10-05 19:48:51 +02:00
|
|
|
|
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;
|
2020-10-11 20:41:10 +02:00
|
|
|
}
|
2020-10-12 21:18:28 +02:00
|
|
|
|
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
|
|
|
|
2021-12-20 15:47:17 +01:00
|
|
|
if (!(await this.vaultTimeoutService.isBiometricLockSet())) {
|
|
|
|
this.send({ command: "biometricUnlock", response: "not enabled" }, appId);
|
2020-10-12 21:18:28 +02:00
|
|
|
|
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
|
|
|
|
2021-12-20 15:47:17 +01:00
|
|
|
const keyB64 = (await this.cryptoService.getKeyFromStorage(KeySuffixOptions.Biometric))
|
|
|
|
.keyB64;
|
|
|
|
|
|
|
|
if (keyB64 != null) {
|
|
|
|
this.send({ command: "biometricUnlock", response: "unlocked", keyB64: keyB64 }, appId);
|
|
|
|
} 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),
|
|
|
|
});
|
|
|
|
}
|
2020-10-05 15:11:37 +02:00
|
|
|
}
|