Make fingerprint validation optional, update readme with debug info for native messaging

This commit is contained in:
Hinton 2020-12-18 15:47:48 +01:00
parent 02a3fbde99
commit e639fa6674
7 changed files with 72 additions and 23 deletions

View File

@ -26,6 +26,18 @@ npm install
npm run electron
```
**Debug Native Messaging**
Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows
for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests
to disk, Consult the [native manifests](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location) documentation for more details of the manigest
format, and the exact locations for the different platforms. *Note* that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
The generated manifests are pre-configured with the production ID for the browser extensions. In order to use them with the development builds, the browser extension ID of the development build
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
It will then be possible to run the desktop application as usual using `npm run electron` and communicate with the browser.
# Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.

View File

@ -98,6 +98,16 @@
</div>
<small class="help-block">{{'enableBrowserIntegrationDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input id="enableBrowserIntegrationFingerprint" type="checkbox" name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint" (change)="saveBrowserIntegrationFingerprint()" [disabled]="!enableBrowserIntegration">
{{'enableBrowserIntegrationFingerprint' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationFingerprintDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">

View File

@ -34,6 +34,7 @@ export class SettingsComponent implements OnInit {
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
@ -146,6 +147,7 @@ export class SettingsComponent implements OnInit {
this.disableFavicons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableBrowserIntegration = await this.storageService.get<boolean>(
ElectronConstants.enableBrowserIntegration);
this.enableBrowserIntegrationFingerprint = await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint);
this.enableMinToTray = await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey);
this.enableCloseToTray = await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey);
this.enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey);
@ -329,6 +331,15 @@ export class SettingsComponent implements OnInit {
await this.storageService.save(ElectronConstants.enableBrowserIntegration, this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveBrowserIntegrationFingerprint() {
await this.storageService.save(ElectronConstants.enableBrowserIntegrationFingerprint, this.enableBrowserIntegrationFingerprint);
}
private callAnalytics(name: string, enabled: boolean) {

View File

@ -135,7 +135,7 @@ const eventService = new EventService(storageService, apiService, userService, c
const systemService = new SystemService(storageService, vaultTimeoutService, messagingService, platformUtilsService,
null);
const nativeMessagingService = new NativeMessagingService(cryptoFunctionService, cryptoService, platformUtilsService,
logService, i18nService, userService, messagingService, vaultTimeoutService);
logService, i18nService, userService, messagingService, vaultTimeoutService, storageService);
const analytics = new Analytics(window, () => isDev(), platformUtilsService, storageService, appIdService);
containerService.attachToGlobal(window);

View File

@ -1453,6 +1453,12 @@
"browserIntegrationMasOnlyDesc": {
"message": "Unfortunately browser integration is only supported in the Mac App Store version for now."
},
"enableBrowserIntegrationFingerprint": {
"message": "Require verification for browser integration"
},
"enableBrowserIntegrationFingerprintDesc": {
"message": "Enable an additional layer of security by requiring fingerprint phrase validation when establishing a link between your desktop and browser. When enabled, this requires user intervention and verification each time a connection is established."
},
"approve": {
"message": "Approve"
},

View File

@ -10,6 +10,7 @@ import { WindowMain } from 'jslib/electron/window.main';
export class NativeMessagingMain {
private connected = false;
private socket: any;
constructor(private logService: LogService, private windowMain: WindowMain, private userPath: string, private appPath: string) {}
@ -19,15 +20,16 @@ export class NativeMessagingMain {
ipc.serve(() => {
ipc.server.on('message', (data: any, socket: any) => {
// This is a ugly hack until electron is updated 7.0.0 which supports ipcMain.invoke
this.socket = socket;
this.windowMain.win.webContents.send('nativeMessaging', data);
ipcMain.once('nativeMessagingReply', (event, msg) => {
if (msg != null) {
this.send(msg, socket);
}
});
});
ipcMain.on('nativeMessagingReply', (event, msg) => {
if (this.socket != null && msg != null) {
this.send(msg, this.socket);
}
})
ipc.server.on('connect', () => {
this.connected = true;
})
@ -36,6 +38,7 @@ export class NativeMessagingMain {
'socket.disconnected',
(socket: any, destroyedSocketID: any) => {
this.connected = false;
this.socket = null;
ipc.log(
'client ' + destroyedSocketID + ' has disconnected!'
);

View File

@ -12,6 +12,8 @@ import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { Utils } from 'jslib/misc/utils';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { StorageService } from 'jslib/abstractions';
import { ElectronConstants } from 'jslib/electron/electronConstants';
const MessageValidTimeout = 10 * 1000;
const EncryptionAlgorithm = 'sha1';
@ -21,7 +23,7 @@ export class NativeMessagingService {
constructor(private cryptoFunctionService: CryptoFunctionService, private cryptoService: CryptoService,
private platformUtilService: PlatformUtilsService, private logService: LogService, private i18nService: I18nService,
private userService: UserService, private messagingService: MessagingService, private vaultTimeoutService: VaultTimeoutService) {
private userService: UserService, private messagingService: MessagingService, private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService) {
ipcRenderer.on('nativeMessaging', async (event: any, message: any) => {
this.messageHandler(message);
});
@ -34,25 +36,30 @@ export class NativeMessagingService {
// Request to setup secure encryption
if (rawMessage.command === 'setupEncryption') {
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), remotePublicKey)).join(' ');
this.messagingService.send('setFocus');
if (await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint)) {
ipcRenderer.send('nativeMessagingReply', {command: 'verifyFingerprint', appId: appId});
// Await confirmation that fingerprint is correct
const submitted = await Swal.fire({
title: 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,
});
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), remotePublicKey)).join(' ');
if (submitted.value !== true) {
return;
this.messagingService.send('setFocus');
// Await confirmation that fingerprint is correct
const submitted = await Swal.fire({
title: 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,
});
if (submitted.value !== true) {
return;
}
}
this.secureCommunication(remotePublicKey, appId);
return;
}