From 51b749b1dccb0a7aad3e512cbf426863bd858f65 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 12 Oct 2020 21:18:28 +0200 Subject: [PATCH] Split native messaging into renderer and background service. Encrypt messages and verify timestamp --- proxy/ipc.ts | 1 - src/app/app.component.ts | 3 +- src/app/services.module.ts | 3 + src/main.ts | 8 +- src/main/messaging.main.ts | 8 +- src/main/nativeMessaging.main.ts | 204 ++++++++++++++++++++++ src/services/nativeMessaging.service.ts | 216 +++--------------------- 7 files changed, 240 insertions(+), 203 deletions(-) create mode 100644 src/main/nativeMessaging.main.ts diff --git a/proxy/ipc.ts b/proxy/ipc.ts index fde39557fe..097fe5455c 100644 --- a/proxy/ipc.ts +++ b/proxy/ipc.ts @@ -17,7 +17,6 @@ export default class IPC { '## connected to bitwarden desktop ##', ipc.config.delay ); - ipc.of.bitwarden.emit('message', 'hello'); }); ipc.of.bitwarden.on('disconnect', () => { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index cc14a1c5e9..2b1fdafc72 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -52,6 +52,7 @@ import { UserService } from 'jslib/abstractions/user.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { ConstantsService } from 'jslib/services/constants.service'; +import { NativeMessagingService } from '../services/nativeMessaging.service'; const BroadcasterSubscriptionId = 'AppComponent'; const IdleTimeout = 60000 * 10; // 10 minutes @@ -97,7 +98,7 @@ export class AppComponent implements OnInit { private searchService: SearchService, private notificationsService: NotificationsService, private platformUtilsService: PlatformUtilsService, private systemService: SystemService, private stateService: StateService, private eventService: EventService, - private policyService: PolicyService) { } + private policyService: PolicyService, private nativeMessagingService: NativeMessagingService) { } ngOnInit() { this.ngZone.runOutsideAngular(() => { diff --git a/src/app/services.module.ts b/src/app/services.module.ts index 5890705f18..d4ff88d740 100644 --- a/src/app/services.module.ts +++ b/src/app/services.module.ts @@ -18,6 +18,7 @@ import { isDev } from 'jslib/electron/utils'; import { DeviceType } from 'jslib/enums/deviceType'; import { I18nService } from '../services/i18n.service'; +import { NativeMessagingService } from '../services/nativeMessaging.service'; import { AuthGuardService } from 'jslib/angular/services/auth-guard.service'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; @@ -128,6 +129,7 @@ const environmentService = new EnvironmentService(apiService, storageService, no const eventService = new EventService(storageService, apiService, userService, cipherService); const systemService = new SystemService(storageService, vaultTimeoutService, messagingService, platformUtilsService, null); +const nativeMessagingService = new NativeMessagingService(cryptoService, platformUtilsService) const analytics = new Analytics(window, () => isDev(), platformUtilsService, storageService, appIdService); containerService.attachToGlobal(window); @@ -211,6 +213,7 @@ export function initFactory(): Function { { provide: EventServiceAbstraction, useValue: eventService }, { provide: PolicyServiceAbstraction, useValue: policyService }, { provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService }, + { provide: NativeMessagingService, useValue: nativeMessagingService }, { provide: APP_INITIALIZER, useFactory: initFactory, diff --git a/src/main.ts b/src/main.ts index 3d1286c2ac..0d1e575367 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,7 +18,7 @@ import { ElectronStorageService } from 'jslib/electron/services/electronStorage. import { TrayMain } from 'jslib/electron/tray.main'; import { UpdaterMain } from 'jslib/electron/updater.main'; import { WindowMain } from 'jslib/electron/window.main'; -import { NativeMessagingService } from './services/nativeMessaging.service'; +import { NativeMessagingMain } from './main/nativeMessaging.main'; export class Main { logService: ElectronLogService; @@ -34,7 +34,7 @@ export class Main { powerMonitorMain: PowerMonitorMain; trayMain: TrayMain; biometricMain: BiometricMain; - nativeMessagingService: NativeMessagingService; + nativeMessagingMain: NativeMessagingMain; constructor() { // Set paths for portable builds @@ -119,7 +119,7 @@ export class Main { this.biometricMain = new BiometricDarwinMain(this.storageService, this.i18nService); } - this.nativeMessagingService = new NativeMessagingService(this.logService, this.biometricMain, app.getPath('userData'), app.getAppPath()); + this.nativeMessagingMain = new NativeMessagingMain(this.logService, this.windowMain, app.getPath('userData'), app.getAppPath()); } bootstrap() { @@ -145,7 +145,7 @@ export class Main { } if (await this.storageService.get(ElectronConstants.enableBrowserIntegration)) { - this.nativeMessagingService.listen(); + this.nativeMessagingMain.listen(); } if (!app.isDefaultProtocolClient('bitwarden')) { diff --git a/src/main/messaging.main.ts b/src/main/messaging.main.ts index f5acb3d029..c7fa60e5ac 100644 --- a/src/main/messaging.main.ts +++ b/src/main/messaging.main.ts @@ -45,12 +45,12 @@ export class MessagingMain { this.main.trayMain.hideToTray(); break; case 'enableBrowserIntegration': - this.main.nativeMessagingService.generateManifests(); - this.main.nativeMessagingService.listen(); + this.main.nativeMessagingMain.generateManifests(); + this.main.nativeMessagingMain.listen(); break; case 'disableBrowserIntegration': - this.main.nativeMessagingService.removeManifests(); - this.main.nativeMessagingService.stop(); + this.main.nativeMessagingMain.removeManifests(); + this.main.nativeMessagingMain.stop(); break; default: break; diff --git a/src/main/nativeMessaging.main.ts b/src/main/nativeMessaging.main.ts new file mode 100644 index 0000000000..1e51db6e20 --- /dev/null +++ b/src/main/nativeMessaging.main.ts @@ -0,0 +1,204 @@ +import { promises as fs, existsSync } from 'fs'; +import * as ipc from 'node-ipc'; +import * as path from 'path'; +import * as util from 'util'; + +import { LogService } from 'jslib/abstractions/log.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { ipcMain, ipcRenderer } from 'electron'; +import { WindowMain } from 'jslib/electron/window.main'; + +export class NativeMessagingMain { + private connected = false; + + constructor(private logService: LogService, private windowMain: WindowMain, private userPath: string, private appPath: string) {} + + listen() { + ipc.config.id = 'bitwarden'; + ipc.config.retry = 1500; + + 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.windowMain.win.webContents.send('nativeMessaging', data); + ipcMain.once('nativeMessagingReply', (event, msg) => { + if (msg != null) { + this.send(msg, socket); + } + }) + }); + + ipc.server.on('connect', () => { + this.connected = true; + }) + + ipc.server.on( + 'socket.disconnected', + (socket: any, destroyedSocketID: any) => { + this.connected = false; + ipc.log( + 'client ' + destroyedSocketID + ' has disconnected!' + ); + } + ); + }); + + ipc.server.start(); + } + + stop() { + ipc.server.stop(); + } + + send(message: object, socket: any) { + ipc.server.emit(socket, 'message', message); + } + + generateManifests() { + const baseJson = { + 'name': 'com.8bit.bitwarden', + 'description': 'Bitwarden desktop <-> browser bridge', + 'path': path.join(this.appPath, 'proxys', this.binaryName()), + 'type': 'stdio', + } + + const firefoxJson = {...baseJson, ...{ 'allowed_origins': ['446900e4-71c2-419f-a6a7-df9c091e268b']}} + const chromeJson = {...baseJson, ...{ 'allowed_origins': ['chrome-extension://ijeheppnniijonkinoakkofcdhdfojda/']}} + + if (!existsSync(path.join(this.userPath, 'browsers'))) { + fs.mkdir(path.join(this.userPath, 'browsers')) + .catch(this.logService.error) + } + + switch (process.platform) { + case 'win32': + const destination = path.join(this.userPath, 'browsers') + this.writeManifest(path.join(destination, 'firefox.json'), firefoxJson); + this.writeManifest(path.join(destination, 'chrome.json'), chromeJson); + + this.createWindowsRegistry('HKLM\\SOFTWARE\\Mozilla\\Firefox', 'HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden', path.join(destination, 'firefox.json')) + this.createWindowsRegistry('HKCU\\SOFTWARE\\Google\\Chrome', 'HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden', path.join(destination, 'chrome.json')) + break; + case 'darwin': + if (existsSync('~/Library/Application Support/Mozilla/')) { + fs.mkdir('~/Library/Application Support/Mozilla/NativeMessagingHosts/'); + this.writeManifest('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json', firefoxJson); + } + + if (existsSync('~/Library/Application Support/Google/Chrome/')) { + fs.mkdir('~/Library/Application Support/Google/Chrome/NativeMessagingHosts/'); + this.writeManifest('~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.8bit.bitwarden.json', chromeJson); + } + break; + case 'linux': + if (existsSync('~/.mozilla/')) { + fs.mkdir('~/.mozilla/native-messaging-hosts'); + this.writeManifest('~/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json', firefoxJson); + } + + if (existsSync('~/.config/google-chrome/')) { + fs.mkdir('~/.config/google-chrome/NativeMessagingHosts/'); + this.writeManifest('~/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json', chromeJson); + } + break; + default: + break; + } + } + + removeManifests() { + switch (process.platform) { + case 'win32': + this.deleteWindowsRegistry('HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden'); + this.deleteWindowsRegistry('HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden'); + break; + case 'darwin': + if (existsSync('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json')) { + fs.unlink('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json') + } + + if (existsSync('~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.8bit.bitwarden.json')) { + fs.unlink('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json') + } + break; + case 'linux': + if (existsSync('~/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json')) { + fs.unlink('~/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json') + } + + if (existsSync('~/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json')) { + fs.unlink('~/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json') + } + break; + default: + break; + } + } + + private writeManifest(destination: string, manifest: object) { + fs.writeFile(destination, JSON.stringify(manifest, null, 2)).catch(this.logService.error); + } + + private binaryName() { + switch (process.platform) { + case 'win32': + return 'app-win.exe' + case 'darwin': + return 'app-linux' + case 'linux': + default: + return 'app-macos' + } + } + + private async createWindowsRegistry(check: string, location: string, jsonFile: string) { + const regedit = require('regedit'); + regedit.setExternalVBSLocation('resources/regedit/vbs'); + + const list = util.promisify(regedit.list); + const createKey = util.promisify(regedit.createKey); + const putValue = util.promisify(regedit.putValue); + + this.logService.debug(`Adding registry: ${location}`) + + // Check installed + try { + await list(check) + } catch { + return; + } + + try { + await createKey(location); + + // Insert path to manifest + const obj: any = {}; + obj[location] = { + 'default': { + value: jsonFile, + type: 'REG_DEFAULT', + }, + } + + return putValue(obj); + } catch (error) { + this.logService.error(error); + } + } + + private async deleteWindowsRegistry(key: string) { + const regedit = require('regedit'); + + const list = util.promisify(regedit.list); + const deleteKey = util.promisify(regedit.deleteKey); + + this.logService.debug(`Removing registry: ${key}`) + + try { + await list(key); + await deleteKey(key); + } catch { + // Do nothing + } + } +} diff --git a/src/services/nativeMessaging.service.ts b/src/services/nativeMessaging.service.ts index b029c4b080..962eb6903c 100644 --- a/src/services/nativeMessaging.service.ts +++ b/src/services/nativeMessaging.service.ts @@ -1,211 +1,34 @@ -import { promises as fs, existsSync } from 'fs'; -import * as ipc from 'node-ipc'; -import * as path from 'path'; -import * as util from 'util'; - -import { LogService } from 'jslib/abstractions/log.service'; -import { BiometricMain } from 'jslib/abstractions/biometric.main'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { PlatformUtilsService } from 'jslib/abstractions'; +import { ipcRenderer } from 'electron'; export class NativeMessagingService { - private connected = false; - - constructor(private logService: LogService, private biometricMain: BiometricMain, private userPath: string, private appPath: string) {} - - listen() { - ipc.config.id = 'bitwarden'; - ipc.config.retry = 1500; - - ipc.serve(() => { - ipc.server.on('message', (data: any, socket: any) => { - this.messageHandler(data, socket); - }); - - ipc.server.on('connect', () => { - this.connected = true; - }) - - ipc.server.on( - 'socket.disconnected', - (socket: any, destroyedSocketID: any) => { - this.connected = false; - ipc.log( - 'client ' + destroyedSocketID + ' has disconnected!' - ); - } - ); + constructor(private cryptoService: CryptoService, private platformUtilService: PlatformUtilsService) { + ipcRenderer.on('nativeMessaging', async (event: any, message: any) => { + this.messageHandler(message); }); - - ipc.server.start(); } - stop() { - ipc.server.stop(); - } + private async messageHandler(rawMessage: any) { + const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage)); - send(message: object, socket: any) { - ipc.server.emit(socket, 'message', message); - } - - generateManifests() { - const baseJson = { - 'name': 'com.8bit.bitwarden', - 'description': 'Bitwarden desktop <-> browser bridge', - 'path': path.join(this.appPath, 'proxys', this.binaryName()), - 'type': 'stdio', - } - - const firefoxJson = {...baseJson, ...{ 'allowed_origins': ['446900e4-71c2-419f-a6a7-df9c091e268b']}} - const chromeJson = {...baseJson, ...{ 'allowed_origins': ['chrome-extension://ijeheppnniijonkinoakkofcdhdfojda/']}} - - if (!existsSync(path.join(this.userPath, 'browsers'))) { - fs.mkdir(path.join(this.userPath, 'browsers')) - .catch(this.logService.error) - } - - switch (process.platform) { - case 'win32': - const destination = path.join(this.userPath, 'browsers') - this.writeManifest(path.join(destination, 'firefox.json'), firefoxJson); - this.writeManifest(path.join(destination, 'chrome.json'), chromeJson); - - this.createWindowsRegistry('HKLM\\SOFTWARE\\Mozilla\\Firefox', 'HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden', path.join(destination, 'firefox.json')) - this.createWindowsRegistry('HKCU\\SOFTWARE\\Google\\Chrome', 'HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden', path.join(destination, 'chrome.json')) - break; - case 'darwin': - if (existsSync('~/Library/Application Support/Mozilla/')) { - fs.mkdir('~/Library/Application Support/Mozilla/NativeMessagingHosts/'); - this.writeManifest('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json', firefoxJson); - } - - if (existsSync('~/Library/Application Support/Google/Chrome/')) { - fs.mkdir('~/Library/Application Support/Google/Chrome/NativeMessagingHosts/'); - this.writeManifest('~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.8bit.bitwarden.json', chromeJson); - } - break; - case 'linux': - if (existsSync('~/.mozilla/')) { - fs.mkdir('~/.mozilla/native-messaging-hosts'); - this.writeManifest('~/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json', firefoxJson); - } - - if (existsSync('~/.config/google-chrome/')) { - fs.mkdir('~/.config/google-chrome/NativeMessagingHosts/'); - this.writeManifest('~/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json', chromeJson); - } - break; - default: - break; - } - } - - removeManifests() { - switch (process.platform) { - case 'win32': - this.deleteWindowsRegistry('HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden'); - this.deleteWindowsRegistry('HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden'); - break; - case 'darwin': - if (existsSync('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json')) { - fs.unlink('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json') - } - - if (existsSync('~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.8bit.bitwarden.json')) { - fs.unlink('~/Library/Application Support/Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json') - } - break; - case 'linux': - if (existsSync('~/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json')) { - fs.unlink('~/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json') - } - - if (existsSync('~/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json')) { - fs.unlink('~/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json') - } - break; - default: - break; - } - } - - private writeManifest(destination: string, manifest: object) { - fs.writeFile(destination, JSON.stringify(manifest, null, 2)).catch(this.logService.error); - } - - private binaryName() { - switch (process.platform) { - case 'win32': - return 'app-win.exe' - case 'darwin': - return 'app-linux' - case 'linux': - default: - return 'app-macos' - } - } - - private async createWindowsRegistry(check: string, location: string, jsonFile: string) { - const regedit = require('regedit'); - regedit.setExternalVBSLocation('resources/regedit/vbs'); - - const list = util.promisify(regedit.list); - const createKey = util.promisify(regedit.createKey); - const putValue = util.promisify(regedit.putValue); - - this.logService.debug(`Adding registry: ${location}`) - - // Check installed - try { - await list(check) - } catch { + if (Math.abs(message.timestamp - Date.now()) > 10*1000) { + console.error("MESSAGE IS TO OLD"); return; } - try { - await createKey(location); - - // Insert path to manifest - const obj: any = {}; - obj[location] = { - 'default': { - value: jsonFile, - type: 'REG_DEFAULT', - }, - } - - return putValue(obj); - } catch (error) { - this.logService.error(error); - } - } - - private async deleteWindowsRegistry(key: string) { - const regedit = require('regedit'); - - const list = util.promisify(regedit.list); - const deleteKey = util.promisify(regedit.deleteKey); - - this.logService.debug(`Removing registry: ${key}`) - - try { - await list(key); - await deleteKey(key); - } catch { - // Do nothing - } - } - - private async messageHandler(message: any, socket: any) { switch (message.command) { case 'biometricUnlock': - if (! this.biometricMain) { - return this.send({command: 'biometricUnlock', response: 'not supported'}, socket) + if (! this.platformUtilService.supportsBiometric()) { + ipcRenderer.send('nativeMessagingSync', ) + return this.send({command: 'biometricUnlock', response: 'not supported'}) } - const response = await this.biometricMain.requestCreate(); + const response = await this.platformUtilService.authenticateBiometric(); if (response) { - this.send({command: 'biometricUnlock', response: 'unlocked'}, socket); + this.send({command: 'biometricUnlock', response: 'unlocked'}); } else { - this.send({command: 'biometricUnlock', response: 'canceled'}, socket); + this.send({command: 'biometricUnlock', response: 'canceled'}); } break; @@ -213,4 +36,11 @@ export class NativeMessagingService { console.error('UNKNOWN COMMAND') } } + + private async send(message: any) { + message.timestamp = Date.now(); + const encrypted = await this.cryptoService.encrypt(JSON.stringify(message)); + + ipcRenderer.send('nativeMessagingReply', encrypted); + } }