2020-10-12 21:18:28 +02:00
|
|
|
import { ipcRenderer } from 'electron';
|
2020-10-19 16:50:33 +02:00
|
|
|
import Swal from 'sweetalert2';
|
2020-10-05 20:05:48 +02:00
|
|
|
|
2020-10-12 21:34:41 +02:00
|
|
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
2020-10-16 17:09:17 +02:00
|
|
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
2020-11-30 14:58:52 +01:00
|
|
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
2020-10-12 21:34:41 +02:00
|
|
|
import { LogService } from 'jslib/abstractions/log.service';
|
2020-11-30 14:58:52 +01:00
|
|
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
|
|
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
|
|
import { UserService } from 'jslib/abstractions/user.service';
|
2020-12-16 17:25:30 +01:00
|
|
|
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
2020-11-30 14:58:52 +01:00
|
|
|
|
2020-12-18 15:47:48 +01:00
|
|
|
import { StorageService } from 'jslib/abstractions';
|
|
|
|
import { ElectronConstants } from 'jslib/electron/electronConstants';
|
2021-02-03 19:21:22 +01:00
|
|
|
import { Utils } from 'jslib/misc/utils';
|
|
|
|
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
2020-10-12 21:34:41 +02:00
|
|
|
|
|
|
|
const MessageValidTimeout = 10 * 1000;
|
2020-10-19 12:21:07 +02:00
|
|
|
const EncryptionAlgorithm = 'sha1';
|
2020-10-12 21:34:41 +02:00
|
|
|
|
2020-10-05 15:11:37 +02:00
|
|
|
export class NativeMessagingService {
|
2020-12-16 15:42:46 +01:00
|
|
|
private sharedSecrets = new Map<string, SymmetricCryptoKey>();
|
2020-10-12 21:34:41 +02:00
|
|
|
|
2020-10-16 17:09:17 +02:00
|
|
|
constructor(private cryptoFunctionService: CryptoFunctionService, private cryptoService: CryptoService,
|
2021-01-15 10:57:09 +01:00
|
|
|
private platformUtilService: PlatformUtilsService, private logService: LogService,
|
|
|
|
private i18nService: I18nService, private userService: UserService, private messagingService: MessagingService,
|
|
|
|
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService) {
|
2020-10-12 21:18:28 +02:00
|
|
|
ipcRenderer.on('nativeMessaging', async (event: any, message: any) => {
|
|
|
|
this.messageHandler(message);
|
2020-10-05 15:11:37 +02:00
|
|
|
});
|
2020-10-05 19:48:51 +02:00
|
|
|
}
|
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
private async messageHandler(msg: any) {
|
|
|
|
const appId = msg.appId;
|
|
|
|
const rawMessage = msg.message;
|
2020-10-21 16:48:40 +02:00
|
|
|
|
|
|
|
// Request to setup secure encryption
|
2020-11-30 15:10:57 +01:00
|
|
|
if (rawMessage.command === 'setupEncryption') {
|
2020-10-19 16:50:33 +02:00
|
|
|
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
|
2020-11-30 15:10:57 +01:00
|
|
|
|
2021-01-15 10:57:09 +01:00
|
|
|
// Valudate the UserId to ensure we are logged into the same account.
|
|
|
|
if (rawMessage.userId !== await this.userService.getUserId()) {
|
|
|
|
ipcRenderer.send('nativeMessagingReply', {command: 'wrongUserId', appId: appId});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-18 15:47:48 +01:00
|
|
|
if (await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint)) {
|
|
|
|
ipcRenderer.send('nativeMessagingReply', {command: 'verifyFingerprint', appId: appId});
|
|
|
|
|
|
|
|
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), remotePublicKey)).join(' ');
|
|
|
|
|
|
|
|
this.messagingService.send('setFocus');
|
|
|
|
|
|
|
|
// Await confirmation that fingerprint is correct
|
|
|
|
const submitted = await Swal.fire({
|
2021-05-13 21:22:52 +02:00
|
|
|
titleText: this.i18nService.t('verifyBrowserTitle'),
|
2020-12-18 15:47:48 +01:00
|
|
|
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,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (submitted.value !== true) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2020-12-22 17:16:12 +01:00
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
this.secureCommunication(remotePublicKey, appId);
|
2020-10-16 17:09:17 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-10-05 20:05:48 +02:00
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecrets.get(appId)));
|
2020-10-19 12:21:07 +02:00
|
|
|
|
2020-10-23 14:40:31 +02:00
|
|
|
// Shared secret is invalidated, force re-authentication
|
|
|
|
if (message == null) {
|
2020-12-16 15:42:46 +01:00
|
|
|
ipcRenderer.send('nativeMessagingReply', {command: 'invalidateEncryption', appId: appId});
|
2020-10-23 14:40:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-12 21:34:41 +02:00
|
|
|
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
|
|
|
this.logService.error('NativeMessage is to old, ignoring.');
|
2020-10-07 15:11:01 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-10-05 19:48:51 +02:00
|
|
|
|
2020-10-11 20:41:10 +02:00
|
|
|
switch (message.command) {
|
|
|
|
case 'biometricUnlock':
|
2020-10-12 21:18:28 +02:00
|
|
|
if (! this.platformUtilService.supportsBiometric()) {
|
2020-12-16 15:42:46 +01:00
|
|
|
return this.send({command: 'biometricUnlock', response: 'not supported'}, appId);
|
2020-10-11 20:41:10 +02:00
|
|
|
}
|
|
|
|
|
2020-12-16 17:25:30 +01:00
|
|
|
if (! await this.vaultTimeoutService.isBiometricLockSet()) {
|
|
|
|
this.send({command: 'biometricUnlock', response: 'not enabled'}, appId);
|
|
|
|
|
|
|
|
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-12 21:18:28 +02:00
|
|
|
const response = await this.platformUtilService.authenticateBiometric();
|
2020-10-11 20:41:10 +02:00
|
|
|
if (response) {
|
2020-12-16 15:42:46 +01:00
|
|
|
this.send({command: 'biometricUnlock', response: 'unlocked', keyB64: (await this.cryptoService.getKey()).keyB64}, appId);
|
2020-10-11 20:41:10 +02:00
|
|
|
} else {
|
2020-12-16 15:42:46 +01:00
|
|
|
this.send({command: 'biometricUnlock', response: 'canceled'}, appId);
|
2020-10-11 20:41:10 +02:00
|
|
|
}
|
2020-10-12 18:03:16 +02:00
|
|
|
|
2020-10-11 20:41:10 +02:00
|
|
|
break;
|
|
|
|
default:
|
2020-10-12 21:34:41 +02:00
|
|
|
this.logService.error('NativeMessage, got unknown command.');
|
2020-10-11 20:41:10 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-12 21:18:28 +02:00
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
private async send(message: any, appId: string) {
|
2020-10-12 21:18:28 +02:00
|
|
|
message.timestamp = Date.now();
|
2020-10-19 12:21:07 +02:00
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
const encrypted = await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecrets.get(appId));
|
2020-10-12 21:18:28 +02:00
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
ipcRenderer.send('nativeMessagingReply', {appId: appId, message: encrypted});
|
2020-10-12 21:18:28 +02:00
|
|
|
}
|
2020-10-16 17:09:17 +02:00
|
|
|
|
2020-12-16 15:42:46 +01:00
|
|
|
private async secureCommunication(remotePublicKey: ArrayBuffer, appId: string) {
|
2020-10-19 12:21:07 +02:00
|
|
|
const secret = await this.cryptoFunctionService.randomBytes(64);
|
2020-12-16 15:42:46 +01:00
|
|
|
this.sharedSecrets.set(appId, new SymmetricCryptoKey(secret));
|
2020-10-16 17:09:17 +02:00
|
|
|
|
2020-10-19 16:50:33 +02:00
|
|
|
const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt(secret, remotePublicKey, EncryptionAlgorithm);
|
2020-12-16 15:42:46 +01:00
|
|
|
ipcRenderer.send('nativeMessagingReply', {appId: appId, command: 'setupEncryption', sharedSecret: Utils.fromBufferToB64(encryptedSecret)});
|
2020-10-16 17:09:17 +02:00
|
|
|
}
|
2020-10-05 15:11:37 +02:00
|
|
|
}
|