From 296ccb68298e7f1de591a16adce59b99cc40152a Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 5 Oct 2020 15:44:19 +0200 Subject: [PATCH 01/21] WIP desktop communication --- src/background.ts | 10 ++++++++++ src/manifest.json | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/background.ts b/src/background.ts index 301fe1a2895..ef62efffaac 100644 --- a/src/background.ts +++ b/src/background.ts @@ -4,3 +4,13 @@ const bitwardenMain = (window as any).bitwardenMain = new MainBackground(); bitwardenMain.bootstrap().then(() => { // Finished bootstrapping }); + +const port = chrome.runtime.connectNative('com.8bit.bitwarden'); + +port.onMessage.addListener((msg: any) => { + console.log('Received' + msg); +}); +port.onDisconnect.addListener(() => { + console.log('Disconnected'); +}); +port.postMessage({ text: 'Hello, my_application' }); diff --git a/src/manifest.json b/src/manifest.json index 718ad3a0e86..ac7745dd36f 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -89,7 +89,8 @@ "http://*/*", "https://*/*", "webRequest", - "webRequestBlocking" + "webRequestBlocking", + "nativeMessaging" ], "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "commands": { From f311101ed941b203c8c55c75f225345c5b3a3ec3 Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 9 Oct 2020 17:16:15 +0200 Subject: [PATCH 02/21] Initial work of biometric unlock for browser --- package-lock.json | 6 +++ package.json | 3 +- src/_locales/en/messages.json | 6 +++ src/background.ts | 10 ----- src/background/contextMenus.background.ts | 4 +- src/background/main.background.ts | 6 ++- src/background/nativeMessaging.background.ts | 40 +++++++++++++++++++ src/browser/browserApi.ts | 8 ++++ src/globals.d.ts | 2 - src/popup/settings/settings.component.html | 4 ++ src/popup/settings/settings.component.ts | 39 ++++++++++++++++++ .../browserPlatformUtils.service.spec.ts | 12 +++--- src/services/browserPlatformUtils.service.ts | 17 +++++--- tsconfig.json | 4 +- 14 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 src/background/nativeMessaging.background.ts diff --git a/package-lock.json b/package-lock.json index 6a8db88c156..fd6c462d7d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -777,6 +777,12 @@ "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=", "dev": true }, + "@types/firefox-webext-browser": { + "version": "78.0.1", + "resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-78.0.1.tgz", + "integrity": "sha512-0d7oiI9K6Y4efP4Crl3JB88zYl7vaRdLtumqz8v6axMF8RCnK0NaGUjL4DnyQ7GLPo98b+s0BSRalaxAXgvPAQ==", + "dev": true + }, "@types/jasmine": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.12.tgz", diff --git a/package.json b/package.json index 837ea32bbfd..3da070d914d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@angular/compiler-cli": "^9.1.12", "@ngtools/webpack": "^9.1.12", "@types/chrome": "^0.0.73", + "@types/firefox-webext-browser": "^78.0.1", "@types/jasmine": "^3.3.12", "@types/lunr": "^2.3.3", "@types/mousetrap": "^1.6.0", @@ -48,7 +49,6 @@ "cross-env": "^5.2.0", "css-loader": "^1.0.0", "del": "^3.0.0", - "mini-css-extract-plugin": "^0.9.0", "file-loader": "^2.0.0", "gulp": "^4.0.0", "gulp-filter": "^5.1.0", @@ -68,6 +68,7 @@ "karma-jasmine": "^2.0.1", "karma-jasmine-html-reporter": "^1.4.0", "karma-typescript": "^4.0.0", + "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.13.1", "sass-loader": "^7.1.0", "style-loader": "^0.23.0", diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index c5667b7b133..5354fe5e4e1 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1247,6 +1247,12 @@ "yourVaultIsLockedPinCode": { "message": "Your vault is locked. Verify your PIN code to continue." }, + "unlockWithBiometric": { + "message": "Unlock with biometric" + }, + "awaitDesktop": { + "message": "Awaiting biometric confirmation from desktop application." + }, "lockWithMasterPassOnRestart": { "message": "Lock with master password on browser restart" }, diff --git a/src/background.ts b/src/background.ts index ef62efffaac..301fe1a2895 100644 --- a/src/background.ts +++ b/src/background.ts @@ -4,13 +4,3 @@ const bitwardenMain = (window as any).bitwardenMain = new MainBackground(); bitwardenMain.bootstrap().then(() => { // Finished bootstrapping }); - -const port = chrome.runtime.connectNative('com.8bit.bitwarden'); - -port.onMessage.addListener((msg: any) => { - console.log('Received' + msg); -}); -port.onDisconnect.addListener(() => { - console.log('Disconnected'); -}); -port.postMessage({ text: 'Hello, my_application' }); diff --git a/src/background/contextMenus.background.ts b/src/background/contextMenus.background.ts index fb335c879f4..7a34acd659e 100644 --- a/src/background/contextMenus.background.ts +++ b/src/background/contextMenus.background.ts @@ -55,8 +55,8 @@ export default class ContextMenusBackground { private async cipherAction(info: any) { const id = info.menuItemId.split('_')[1]; if (id === 'noop') { - if (chrome.browserAction && chrome.browserAction.openPopup) { - chrome.browserAction.openPopup(); + if (chrome.browserAction && (chrome.browserAction as any).openPopup) { + (chrome.browserAction as any).openPopup(); } return; } diff --git a/src/background/main.background.ts b/src/background/main.background.ts index fa30ca24816..c5a0cf05383 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -82,6 +82,7 @@ import I18nService from '../services/i18n.service'; import { PopupUtilsService } from '../popup/services/popup-utils.service'; import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service'; +import { NativeMessagingBackground } from './nativeMessaging.background'; export default class MainBackground { messagingService: MessagingServiceAbstraction; @@ -137,8 +138,11 @@ export default class MainBackground { private menuOptionsLoaded: any[] = []; private syncTimeout: any; private isSafari: boolean; + nativeMessagingBackground: NativeMessagingBackground; constructor() { + this.nativeMessagingBackground = new NativeMessagingBackground(); + // Services this.messagingService = new BrowserMessagingService(); this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService, @@ -146,7 +150,7 @@ export default class MainBackground { if (this.systemService != null) { this.systemService.clearClipboard(clipboardValue, clearMs); } - }); + }, this.nativeMessagingBackground); this.storageService = new BrowserStorageService(this.platformUtilsService); this.secureStorageService = new BrowserStorageService(this.platformUtilsService); this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts new file mode 100644 index 00000000000..dbb673dc5f8 --- /dev/null +++ b/src/background/nativeMessaging.background.ts @@ -0,0 +1,40 @@ +import { BrowserApi } from "../browser/browserApi"; + +export class NativeMessagingBackground { + private connected = false; + private port: browser.runtime.Port | chrome.runtime.Port; + + private resolver: any = null; + + connect() { + this.port = BrowserApi.connectNative("com.8bit.bitwarden"); + + this.connected = true; + this.port.onMessage.addListener((msg: any) => { + if (this.resolver) { + this.resolver(msg); + } else { + console.error('NO RESOLVER'); + } + }); + this.port.onDisconnect.addListener(() => { + this.connected = false; + console.log('Disconnected'); + }); + } + + send(message: object) { + // If not connected, try to connect + if (!this.connected) { + this.connect(); + } + + this.port.postMessage(message); + } + + await(): Promise { + return new Promise((resolve, reject) => { + this.resolver = resolve; + }); + } +} diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 2e4a7f2d039..1ee54a45b18 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -221,4 +221,12 @@ export class BrowserApi { }); } } + + static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port { + if (BrowserApi.isWebExtensionsApi) { + return browser.runtime.connectNative(application); + } else if (BrowserApi.isChromeApi) { + return chrome.runtime.connectNative(application); + } + } } diff --git a/src/globals.d.ts b/src/globals.d.ts index 2868ff5ae7a..120d1194340 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,6 +1,4 @@ declare function escape(s: string): string; declare function unescape(s: string): string; declare var opr: any; -declare var chrome: any; -declare var browser: any; declare var safari: any; diff --git a/src/popup/settings/settings.component.html b/src/popup/settings/settings.component.html index 61e8d7088dd..6439d6e42af 100644 --- a/src/popup/settings/settings.component.html +++ b/src/popup/settings/settings.component.html @@ -42,6 +42,10 @@ +
+ + +
{{'lockNow' | i18n}}
diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index a831a994bfa..bbdf30cdb35 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -51,6 +51,7 @@ export class SettingsComponent implements OnInit { vaultTimeoutActions: any[]; vaultTimeoutAction: string; pin: boolean = null; + biometric: boolean = null; previousVaultTimeout: number = null; constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, @@ -100,6 +101,7 @@ export class SettingsComponent implements OnInit { const pinSet = await this.vaultTimeoutService.isPinLockSet(); this.pin = pinSet[0] || pinSet[1]; + this.biometric = await this.vaultTimeoutService.isBiometricLockSet(); } async saveVaultTimeout(newValue: number) { @@ -204,6 +206,43 @@ export class SettingsComponent implements OnInit { } } + async updateBiometric() { + const current = this.biometric; + if (this.biometric) { + this.biometric = false; + } else { + const div = document.createElement('div'); + div.innerHTML = `
${this.i18nService.t('awaitDesktop')}
`; + + const submitted = Swal.fire({ + heightAuto: false, + buttonsStyling: false, + html: div, + showCancelButton: true, + cancelButtonText: this.i18nService.t('cancel'), + showConfirmButton: false, + }); + + // TODO: Show waiting message + this.biometric = await this.platformUtilsService.authenticateBiometric(); + Swal.close(); + + if (this.biometric == false) { + this.platformUtilsService.showToast("error", "Unable to enable biometrics", "Ensure the desktop application is running, and browser integration is enabled."); + } + } + if (this.biometric === current) { + return; + } + if (this.biometric) { + await this.storageService.save(ConstantsService.biometricUnlockKey, true); + } else { + await this.storageService.remove(ConstantsService.biometricUnlockKey); + } + this.vaultTimeoutService.biometricLocked = false; + await this.cryptoService.toggleKey(); + } + async lock() { this.analytics.eventTrack.next({ action: 'Lock Now' }); await this.vaultTimeoutService.lock(true); diff --git a/src/services/browserPlatformUtils.service.spec.ts b/src/services/browserPlatformUtils.service.spec.ts index 74881853a60..6b3711ef477 100644 --- a/src/services/browserPlatformUtils.service.spec.ts +++ b/src/services/browserPlatformUtils.service.spec.ts @@ -27,7 +27,7 @@ describe('Browser Utils Service', () => { value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', }); - const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); + const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.ChromeExtension); }); @@ -37,7 +37,7 @@ describe('Browser Utils Service', () => { value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0', }); - const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); + const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension); }); @@ -52,7 +52,7 @@ describe('Browser Utils Service', () => { value: {}, }); - const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); + const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.OperaExtension); }); @@ -62,7 +62,7 @@ describe('Browser Utils Service', () => { value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43', }); - const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); + const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension); }); @@ -77,7 +77,7 @@ describe('Browser Utils Service', () => { value: true, }); - const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); + const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension); Object.defineProperty(window, 'safariAppExtension', { @@ -92,7 +92,7 @@ describe('Browser Utils Service', () => { value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40', }); - const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); + const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension); }); }); diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index 488f0d00cd0..63a549fcc2d 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -1,5 +1,6 @@ import { BrowserApi } from '../browser/browserApi'; import { SafariApp } from '../browser/safariApp'; +import { NativeMessagingBackground } from '../background/nativeMessaging.background'; import { DeviceType } from 'jslib/enums/deviceType'; @@ -18,7 +19,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService private analyticsIdCache: string = null; constructor(private messagingService: MessagingService, - private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void) { } + private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, + private nativeMessagingBackground: NativeMessagingBackground) { } getDevice(): DeviceType { if (this.deviceCache) { @@ -288,13 +290,18 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService } supportsBiometric() { - return Promise.resolve(false); + return Promise.resolve(true); } - authenticateBiometric() { - return Promise.resolve(false); - } + async authenticateBiometric() { + const responsePromise = this.nativeMessagingBackground.await(); + this.nativeMessagingBackground.send({'command': 'biometricUnlock'}); + const response = await responsePromise; + + return response.response == 'unlocked'; + } + sidebarViewName(): string { if ((window as any).chrome.sidebarAction && this.isFirefox()) { return 'sidebar'; diff --git a/tsconfig.json b/tsconfig.json index e1b5cca4130..a1991acf1a5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,9 @@ "sourceMap": true, "types": [ "jasmine", - "sweetalert2" + "sweetalert2", + "@types/chrome", + "@types/firefox-webext-browser" ], "baseUrl": ".", "paths": { From 01ffa27fccc448b177e8aba0f19f68cde91c1d41 Mon Sep 17 00:00:00 2001 From: Hinton Date: Sun, 11 Oct 2020 20:42:09 +0200 Subject: [PATCH 03/21] Add unlock using biometry to lock screen --- src/popup/accounts/lock.component.html | 5 +++++ src/popup/accounts/lock.component.ts | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/popup/accounts/lock.component.html b/src/popup/accounts/lock.component.html index e909d91e219..773cc20d493 100644 --- a/src/popup/accounts/lock.component.html +++ b/src/popup/accounts/lock.component.html @@ -36,6 +36,11 @@ {{'loggedInAsOn' | i18n : email : webVaultHostname}} +

{{'logOut' | i18n}}

diff --git a/src/popup/accounts/lock.component.ts b/src/popup/accounts/lock.component.ts index ed7bc001089..f7c6729219f 100644 --- a/src/popup/accounts/lock.component.ts +++ b/src/popup/accounts/lock.component.ts @@ -13,6 +13,7 @@ import { UserService } from 'jslib/abstractions/user.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { LockComponent as BaseLockComponent } from 'jslib/angular/components/lock.component'; +import Swal from 'sweetalert2'; @Component({ selector: 'app-lock', @@ -36,4 +37,26 @@ export class LockComponent extends BaseLockComponent { document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus(); }, 100); } + + async unlockBiometric() { + if (!this.biometricLock) { + return; + } + + const div = document.createElement('div'); + div.innerHTML = `
${this.i18nService.t('awaitDesktop')}
`; + + const submitted = Swal.fire({ + heightAuto: false, + buttonsStyling: false, + html: div, + showCancelButton: true, + cancelButtonText: this.i18nService.t('cancel'), + showConfirmButton: false, + }); + + await super.unlockBiometric(); + + Swal.close(); + } } From 5eb0ce1e099b267dcc67ba496faa4565cb71cebe Mon Sep 17 00:00:00 2001 From: Hinton Date: Sun, 11 Oct 2020 20:45:25 +0200 Subject: [PATCH 04/21] Fix linting errors --- src/background/main.background.ts | 2 +- src/background/nativeMessaging.background.ts | 6 +++--- src/popup/settings/settings.component.ts | 4 ++-- src/services/browserPlatformUtils.service.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/background/main.background.ts b/src/background/main.background.ts index c5a0cf05383..19758b94374 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -138,7 +138,7 @@ export default class MainBackground { private menuOptionsLoaded: any[] = []; private syncTimeout: any; private isSafari: boolean; - nativeMessagingBackground: NativeMessagingBackground; + private nativeMessagingBackground: NativeMessagingBackground; constructor() { this.nativeMessagingBackground = new NativeMessagingBackground(); diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index dbb673dc5f8..30028acb8c4 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,4 +1,4 @@ -import { BrowserApi } from "../browser/browserApi"; +import { BrowserApi } from '../browser/browserApi'; export class NativeMessagingBackground { private connected = false; @@ -7,19 +7,19 @@ export class NativeMessagingBackground { private resolver: any = null; connect() { - this.port = BrowserApi.connectNative("com.8bit.bitwarden"); + this.port = BrowserApi.connectNative('com.8bit.bitwarden'); this.connected = true; this.port.onMessage.addListener((msg: any) => { if (this.resolver) { this.resolver(msg); } else { + // tslint:disable-next-line console.error('NO RESOLVER'); } }); this.port.onDisconnect.addListener(() => { this.connected = false; - console.log('Disconnected'); }); } diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index bbdf30cdb35..6d1c06463b8 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -227,8 +227,8 @@ export class SettingsComponent implements OnInit { this.biometric = await this.platformUtilsService.authenticateBiometric(); Swal.close(); - if (this.biometric == false) { - this.platformUtilsService.showToast("error", "Unable to enable biometrics", "Ensure the desktop application is running, and browser integration is enabled."); + if (this.biometric === false) { + this.platformUtilsService.showToast('error', 'Unable to enable biometrics', 'Ensure the desktop application is running, and browser integration is enabled.'); } } if (this.biometric === current) { diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index 63a549fcc2d..fe7e14b7991 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -299,9 +299,9 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService const response = await responsePromise; - return response.response == 'unlocked'; + return response.response === 'unlocked'; } - + sidebarViewName(): string { if ((window as any).chrome.sidebarAction && this.isFirefox()) { return 'sidebar'; From 894d245361c404323fa8d36247d4327ad5c214d7 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 12 Oct 2020 18:01:34 +0200 Subject: [PATCH 05/21] Ensure biometric unlock works even if popup is not in focus --- src/_locales/en/messages.json | 5 +- src/background/main.background.ts | 12 +++-- src/background/nativeMessaging.background.ts | 44 ++++++++++++++---- src/background/runtime.background.ts | 1 + src/popup/settings/settings.component.ts | 49 +++++++++++--------- src/services/browserPlatformUtils.service.ts | 11 ++--- 6 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 5354fe5e4e1..ca584a831b2 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1251,7 +1251,10 @@ "message": "Unlock with biometric" }, "awaitDesktop": { - "message": "Awaiting biometric confirmation from desktop application." + "message": "Awaiting confirmation from desktop" + }, + "awaitDesktopDesc": { + "message": "Please confirm using biometrics in the Bitwarden desktop application to enable biometrics for browser." }, "lockWithMasterPassOnRestart": { "message": "Lock with master password on browser restart" diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 19758b94374..27916cef21f 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -141,8 +141,6 @@ export default class MainBackground { private nativeMessagingBackground: NativeMessagingBackground; constructor() { - this.nativeMessagingBackground = new NativeMessagingBackground(); - // Services this.messagingService = new BrowserMessagingService(); this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService, @@ -150,7 +148,14 @@ export default class MainBackground { if (this.systemService != null) { this.systemService.clearClipboard(clipboardValue, clearMs); } - }, this.nativeMessagingBackground); + }, + () => { + if (this.nativeMessagingBackground != null) { + const promise = this.nativeMessagingBackground.await(); + this.nativeMessagingBackground.send({command: 'biometricUnlock'}) + return promise.then((result) => result.response === 'unlocked'); + } + }); this.storageService = new BrowserStorageService(this.platformUtilsService); this.secureStorageService = new BrowserStorageService(this.platformUtilsService); this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); @@ -228,6 +233,7 @@ export default class MainBackground { this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.environmentService); + this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.vaultTimeoutService, this.runtimeBackground); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 30028acb8c4..7b3b93c7ff8 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,4 +1,8 @@ +import { CryptoService, VaultTimeoutService } from 'jslib/abstractions'; +import { StorageService } from 'jslib/abstractions/storage.service'; +import { ConstantsService } from 'jslib/services'; import { BrowserApi } from '../browser/browserApi'; +import RuntimeBackground from './runtime.background'; export class NativeMessagingBackground { private connected = false; @@ -6,18 +10,16 @@ export class NativeMessagingBackground { private resolver: any = null; + constructor(private storageService: StorageService, private cryptoService: CryptoService, + private vaultTimeoutService: VaultTimeoutService, private runtimeBackground: RuntimeBackground) {} + connect() { this.port = BrowserApi.connectNative('com.8bit.bitwarden'); this.connected = true; - this.port.onMessage.addListener((msg: any) => { - if (this.resolver) { - this.resolver(msg); - } else { - // tslint:disable-next-line - console.error('NO RESOLVER'); - } - }); + + this.port.onMessage.addListener((msg) => this.onMessage(msg)); + this.port.onDisconnect.addListener(() => { this.connected = false; }); @@ -37,4 +39,30 @@ export class NativeMessagingBackground { this.resolver = resolve; }); } + + private async onMessage(msg: any) { + switch(msg.command) { + case 'biometricUnlock': { + await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); + + const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey); + if (enabled === null || enabled === false) { + if (msg.response === 'unlocked') { + await this.storageService.save(ConstantsService.biometricUnlockKey, true); + } + + await this.cryptoService.toggleKey(); + } + + if (this.vaultTimeoutService.biometricLocked) { + this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); + this.vaultTimeoutService.biometricLocked = false; + } + } + } + + if (this.resolver) { + this.resolver(msg); + } + } } diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index aea6b05b1f3..8f0ebf7cd62 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -22,6 +22,7 @@ import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { BrowserApi } from '../browser/browserApi'; import MainBackground from './main.background'; +import { NativeMessagingBackground } from './nativeMessaging.background'; import { Analytics } from 'jslib/misc'; import { Utils } from 'jslib/misc/utils'; diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index 6d1c06463b8..1029fe29f13 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -51,7 +51,7 @@ export class SettingsComponent implements OnInit { vaultTimeoutActions: any[]; vaultTimeoutAction: string; pin: boolean = null; - biometric: boolean = null; + biometric: boolean = false; previousVaultTimeout: number = null; constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, @@ -207,40 +207,45 @@ export class SettingsComponent implements OnInit { } async updateBiometric() { - const current = this.biometric; if (this.biometric) { this.biometric = false; + // TODO: Remove biometric stuff + await this.storageService.remove(ConstantsService.biometricUnlockKey); + this.vaultTimeoutService.biometricLocked = false; } else { - const div = document.createElement('div'); - div.innerHTML = `
${this.i18nService.t('awaitDesktop')}
`; - const submitted = Swal.fire({ heightAuto: false, buttonsStyling: false, - html: div, + title: this.i18nService.t('awaitDesktop'), + text: this.i18nService.t('awaitDesktopDesc'), + icon: 'info', + iconHtml: '', showCancelButton: true, cancelButtonText: this.i18nService.t('cancel'), showConfirmButton: false, + allowOutsideClick: false, }); - // TODO: Show waiting message - this.biometric = await this.platformUtilsService.authenticateBiometric(); - Swal.close(); + await this.storageService.save(ConstantsService.biometricAwaitingAcceptance, true); + await this.cryptoService.toggleKey(); - if (this.biometric === false) { - this.platformUtilsService.showToast('error', 'Unable to enable biometrics', 'Ensure the desktop application is running, and browser integration is enabled.'); - } + await Promise.race([ + submitted.then((result) => { + if (result.dismiss === Swal.DismissReason.cancel) { + this.biometric = false; + this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); + } + }), + this.platformUtilsService.authenticateBiometric().then((result) => { + this.biometric = result; + + Swal.close(); + if (this.biometric === false) { + this.platformUtilsService.showToast('error', 'Unable to enable biometrics', 'Ensure the desktop application is running, and browser integration is enabled.'); + } + }) + ]); } - if (this.biometric === current) { - return; - } - if (this.biometric) { - await this.storageService.save(ConstantsService.biometricUnlockKey, true); - } else { - await this.storageService.remove(ConstantsService.biometricUnlockKey); - } - this.vaultTimeoutService.biometricLocked = false; - await this.cryptoService.toggleKey(); } async lock() { diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index fe7e14b7991..e0439f6bcb9 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -20,7 +20,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService constructor(private messagingService: MessagingService, private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, - private nativeMessagingBackground: NativeMessagingBackground) { } + private biometricCallback: () => Promise) { } getDevice(): DeviceType { if (this.deviceCache) { @@ -293,13 +293,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService return Promise.resolve(true); } - async authenticateBiometric() { - const responsePromise = this.nativeMessagingBackground.await(); - this.nativeMessagingBackground.send({'command': 'biometricUnlock'}); - - const response = await responsePromise; - - return response.response === 'unlocked'; + authenticateBiometric() { + return this.biometricCallback(); } sidebarViewName(): string { From a77cca82c861e33ee1b0939ef3f87dba274376a1 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 12 Oct 2020 21:18:47 +0200 Subject: [PATCH 06/21] Encrypt messages and verify timestamp. --- src/background/nativeMessaging.background.ts | 22 ++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 7b3b93c7ff8..d1ef5577f03 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -25,13 +25,16 @@ export class NativeMessagingBackground { }); } - send(message: object) { + async send(message: any) { // If not connected, try to connect if (!this.connected) { this.connect(); } - this.port.postMessage(message); + message.timestamp = Date.now(); + + const encrypted = await this.cryptoService.encrypt(JSON.stringify(message)); + this.port.postMessage(encrypted); } await(): Promise { @@ -40,14 +43,21 @@ export class NativeMessagingBackground { }); } - private async onMessage(msg: any) { - switch(msg.command) { + private async onMessage(rawMessage: any) { + const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage)); + + if (Math.abs(message.timestamp - Date.now()) > 10*1000) { + console.error("MESSAGE IS TO OLD"); + return; + } + + switch(message.command) { case 'biometricUnlock': { await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey); if (enabled === null || enabled === false) { - if (msg.response === 'unlocked') { + if (message.response === 'unlocked') { await this.storageService.save(ConstantsService.biometricUnlockKey, true); } @@ -62,7 +72,7 @@ export class NativeMessagingBackground { } if (this.resolver) { - this.resolver(msg); + this.resolver(message); } } } From 41134aee9858b81e4623738b0a05c58879010098 Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 16 Oct 2020 11:09:49 +0200 Subject: [PATCH 07/21] Minor cleanup --- src/background/nativeMessaging.background.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index d1ef5577f03..21689661935 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,9 +1,11 @@ -import { CryptoService, VaultTimeoutService } from 'jslib/abstractions'; +import { CryptoService, LogService, VaultTimeoutService } from 'jslib/abstractions'; import { StorageService } from 'jslib/abstractions/storage.service'; import { ConstantsService } from 'jslib/services'; import { BrowserApi } from '../browser/browserApi'; import RuntimeBackground from './runtime.background'; +const MessageValidTimeout = 10 * 1000; + export class NativeMessagingBackground { private connected = false; private port: browser.runtime.Port | chrome.runtime.Port; @@ -19,7 +21,7 @@ export class NativeMessagingBackground { this.connected = true; this.port.onMessage.addListener((msg) => this.onMessage(msg)); - + this.port.onDisconnect.addListener(() => { this.connected = false; }); @@ -46,12 +48,13 @@ export class NativeMessagingBackground { private async onMessage(rawMessage: any) { const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage)); - if (Math.abs(message.timestamp - Date.now()) > 10*1000) { - console.error("MESSAGE IS TO OLD"); + if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { + // tslint:disable-next-line + console.error('NativeMessage is to old, ignoring.'); return; } - switch(message.command) { + switch (message.command) { case 'biometricUnlock': { await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); @@ -60,7 +63,7 @@ export class NativeMessagingBackground { if (message.response === 'unlocked') { await this.storageService.save(ConstantsService.biometricUnlockKey, true); } - + await this.cryptoService.toggleKey(); } @@ -69,6 +72,9 @@ export class NativeMessagingBackground { this.vaultTimeoutService.biometricLocked = false; } } + default: + // tslint:disable-next-line + console.error('NativeMessage, got unknown command.'); } if (this.resolver) { From 90bba83ae53f9a05e1581ee2df96723e09bd7373 Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 16 Oct 2020 17:08:53 +0200 Subject: [PATCH 08/21] wip --- src/background/main.background.ts | 2 +- src/background/nativeMessaging.background.ts | 44 ++++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 27916cef21f..0295430661a 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -233,7 +233,7 @@ export default class MainBackground { this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.environmentService); - this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.vaultTimeoutService, this.runtimeBackground); + this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService, this.vaultTimeoutService, this.runtimeBackground); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 21689661935..7ef3e6f74be 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,19 +1,27 @@ import { CryptoService, LogService, VaultTimeoutService } from 'jslib/abstractions'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { StorageService } from 'jslib/abstractions/storage.service'; +import { Utils } from 'jslib/misc/utils'; import { ConstantsService } from 'jslib/services'; import { BrowserApi } from '../browser/browserApi'; import RuntimeBackground from './runtime.background'; const MessageValidTimeout = 10 * 1000; +const EncryptionAlgorithm = 'sha256'; export class NativeMessagingBackground { private connected = false; private port: browser.runtime.Port | chrome.runtime.Port; private resolver: any = null; + publicKey: ArrayBuffer; + privateKey: ArrayBuffer; + private secureSetupResolve: any = null; + remotePublicKey: ArrayBufferLike; constructor(private storageService: StorageService, private cryptoService: CryptoService, - private vaultTimeoutService: VaultTimeoutService, private runtimeBackground: RuntimeBackground) {} + private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService, + private runtimeBackground: RuntimeBackground) {} connect() { this.port = BrowserApi.connectNative('com.8bit.bitwarden'); @@ -33,9 +41,13 @@ export class NativeMessagingBackground { this.connect(); } + if (this.publicKey == null) { + await this.secureCommunication(); + } + message.timestamp = Date.now(); - const encrypted = await this.cryptoService.encrypt(JSON.stringify(message)); + const encrypted = await this.cryptoFunctionService.rsaEncrypt(Buffer.from(JSON.stringify(message)), this.remotePublicKey, EncryptionAlgorithm); this.port.postMessage(encrypted); } @@ -55,7 +67,11 @@ export class NativeMessagingBackground { } switch (message.command) { - case 'biometricUnlock': { + case 'setupEncryption': + this.remotePublicKey = Utils.fromB64ToArray(message.publicKey).buffer; + this.secureSetupResolve(); + break; + case 'biometricUnlock': await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey); @@ -71,7 +87,7 @@ export class NativeMessagingBackground { this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); this.vaultTimeoutService.biometricLocked = false; } - } + break; default: // tslint:disable-next-line console.error('NativeMessage, got unknown command.'); @@ -81,4 +97,24 @@ export class NativeMessagingBackground { this.resolver(message); } } + + private async secureCommunication() { + // Using crypto function service directly since we cannot encrypt the private key as + // master key might not be available + [this.publicKey, this.privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); + + this.sendUnencrypted({command: 'setupEncryption', publicKey: Utils.fromBufferToB64(this.publicKey)}); + + return new Promise((resolve, reject) => this.secureSetupResolve = resolve); + } + + private async sendUnencrypted(message: any) { + if (!this.connected) { + this.connect(); + } + + message.timestamp = Date.now(); + + this.port.postMessage(message); + } } From 378f6037090f46978ed46c6b75af7c4b62f9b857 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 19 Oct 2020 12:20:45 +0200 Subject: [PATCH 09/21] Setup new encryption flow --- src/background/nativeMessaging.background.ts | 27 ++++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 7ef3e6f74be..afefab21f29 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -2,22 +2,23 @@ import { CryptoService, LogService, VaultTimeoutService } from 'jslib/abstractio import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { Utils } from 'jslib/misc/utils'; +import { SymmetricCryptoKey } from 'jslib/models/domain'; import { ConstantsService } from 'jslib/services'; import { BrowserApi } from '../browser/browserApi'; import RuntimeBackground from './runtime.background'; const MessageValidTimeout = 10 * 1000; -const EncryptionAlgorithm = 'sha256'; +const EncryptionAlgorithm = 'sha1'; export class NativeMessagingBackground { private connected = false; private port: browser.runtime.Port | chrome.runtime.Port; private resolver: any = null; - publicKey: ArrayBuffer; - privateKey: ArrayBuffer; + private publicKey: ArrayBuffer; + private privateKey: ArrayBuffer = null; private secureSetupResolve: any = null; - remotePublicKey: ArrayBufferLike; + private sharedSecret: SymmetricCryptoKey; constructor(private storageService: StorageService, private cryptoService: CryptoService, private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService, @@ -41,13 +42,13 @@ export class NativeMessagingBackground { this.connect(); } - if (this.publicKey == null) { + if (this.sharedSecret == null) { await this.secureCommunication(); } message.timestamp = Date.now(); - const encrypted = await this.cryptoFunctionService.rsaEncrypt(Buffer.from(JSON.stringify(message)), this.remotePublicKey, EncryptionAlgorithm); + const encrypted = await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret); this.port.postMessage(encrypted); } @@ -58,7 +59,15 @@ export class NativeMessagingBackground { } private async onMessage(rawMessage: any) { - const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage)); + if (rawMessage.command === 'setupEncryption') { + const encrypted = Utils.fromB64ToArray(rawMessage.sharedSecret); + const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm); + + this.sharedSecret = new SymmetricCryptoKey(decrypted); + this.secureSetupResolve(); + return; + } + const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret)); if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { // tslint:disable-next-line @@ -67,10 +76,6 @@ export class NativeMessagingBackground { } switch (message.command) { - case 'setupEncryption': - this.remotePublicKey = Utils.fromB64ToArray(message.publicKey).buffer; - this.secureSetupResolve(); - break; case 'biometricUnlock': await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); From a659c86373ee43ebbd8c1471cb3ed71d98630da7 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 19 Oct 2020 16:50:25 +0200 Subject: [PATCH 10/21] Show fingerprint message --- src/_locales/en/messages.json | 9 +++++++++ src/background/main.background.ts | 3 ++- src/background/nativeMessaging.background.ts | 17 +++++++++++++++-- src/popup/app.component.ts | 1 + 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index ca584a831b2..be4275d8bb5 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1362,5 +1362,14 @@ }, "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." + }, + "ok": { + "message": "Ok" + }, + "desktopSyncVerificationTitle": { + "message": "Desktop sync verification" + }, + "desktopIntegrationVerificationText": { + "message": "Please verify that the desktop application shows this fingerprint: " } } diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 0295430661a..537b0222bf5 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -233,7 +233,8 @@ export default class MainBackground { this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.environmentService); - this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService, this.vaultTimeoutService, this.runtimeBackground); + this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService, + this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index afefab21f29..e80f8fc00fa 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,4 +1,6 @@ -import { CryptoService, LogService, VaultTimeoutService } from 'jslib/abstractions'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { Utils } from 'jslib/misc/utils'; @@ -6,6 +8,8 @@ import { SymmetricCryptoKey } from 'jslib/models/domain'; import { ConstantsService } from 'jslib/services'; import { BrowserApi } from '../browser/browserApi'; import RuntimeBackground from './runtime.background'; +import { UserService } from 'jslib/abstractions/user.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; const MessageValidTimeout = 10 * 1000; const EncryptionAlgorithm = 'sha1'; @@ -22,7 +26,8 @@ export class NativeMessagingBackground { constructor(private storageService: StorageService, private cryptoService: CryptoService, private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService, - private runtimeBackground: RuntimeBackground) {} + private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService, + private messagingService: MessagingService) {} connect() { this.port = BrowserApi.connectNative('com.8bit.bitwarden'); @@ -109,6 +114,14 @@ export class NativeMessagingBackground { [this.publicKey, this.privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); this.sendUnencrypted({command: 'setupEncryption', publicKey: Utils.fromBufferToB64(this.publicKey)}); + const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' '); + + this.messagingService.send('showDialog', { + html: `${this.i18nService.t('desktopIntegrationVerificationText')}

${fingerprint}.`, + title: this.i18nService.t('desktopSyncVerificationTitle'), + confirmText: this.i18nService.t('ok'), + type: 'warning', + }); return new Promise((resolve, reject) => this.secureSetupResolve = resolve); } diff --git a/src/popup/app.component.ts b/src/popup/app.component.ts index 1daee7f854c..5e10d08ecb3 100644 --- a/src/popup/app.component.ts +++ b/src/popup/app.component.ts @@ -241,6 +241,7 @@ export class AppComponent implements OnInit { icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml. iconHtml: iconClasses != null ? `` : undefined, text: msg.text, + html: msg.html, title: msg.title, showCancelButton: (cancelText != null), cancelButtonText: cancelText, From a3dbf8b65df792ff17f312987e4d19ce53bde3ba Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 19 Oct 2020 18:34:40 +0200 Subject: [PATCH 11/21] Unlock using master key from desktop --- src/background/main.background.ts | 2 +- src/background/nativeMessaging.background.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 537b0222bf5..29651d2fcd4 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -161,7 +161,7 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService); this.cryptoService = new CryptoService(this.storageService, this.secureStorageService, - this.cryptoFunctionService); + this.cryptoFunctionService, this.platformUtilsService); this.tokenService = new TokenService(this.storageService); this.appIdService = new AppIdService(this.storageService); this.apiService = new ApiService(this.tokenService, this.platformUtilsService, diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index e80f8fc00fa..caebdc8e277 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -94,8 +94,9 @@ export class NativeMessagingBackground { } if (this.vaultTimeoutService.biometricLocked) { - this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); + this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)); this.vaultTimeoutService.biometricLocked = false; + this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); } break; default: From 90642983098b5e50d3957e93fb7f9a59541b12fd Mon Sep 17 00:00:00 2001 From: Hinton Date: Wed, 21 Oct 2020 15:56:10 +0200 Subject: [PATCH 12/21] Display error message when browser integration is disabled, or desktop not running --- src/_locales/en/messages.json | 12 +++++ src/background/nativeMessaging.background.ts | 51 ++++++++++++++++---- src/browser/browserApi.ts | 8 +++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index be4275d8bb5..ec562879dfe 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1371,5 +1371,17 @@ }, "desktopIntegrationVerificationText": { "message": "Please verify that the desktop application shows this fingerprint: " + }, + "desktopIntegrationDisabledTitle": { + "message": "Browser integration is not enabled" + }, + "desktopIntegrationDisabledDesc": { + "message": "Browser integration is not enabled in the Bitwarden Desktop Application. Please enable it in the settings within the desktop application." + }, + "startDesktopTitle": { + "message": "Start the Bitwarden Desktop Application" + }, + "startDesktopDesc": { + "message": "The bitwarden desktop application needs to be started before this function can be used." } } diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index caebdc8e277..69389358702 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -16,6 +16,7 @@ const EncryptionAlgorithm = 'sha1'; export class NativeMessagingBackground { private connected = false; + private connecting: boolean; private port: browser.runtime.Port | chrome.runtime.Port; private resolver: any = null; @@ -29,22 +30,54 @@ export class NativeMessagingBackground { private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService, private messagingService: MessagingService) {} - connect() { - this.port = BrowserApi.connectNative('com.8bit.bitwarden'); + async connect() { + return new Promise((resolve, reject) => { + this.port = BrowserApi.connectNative('com.8bit.bitwarden'); - this.connected = true; + this.connecting = true; - this.port.onMessage.addListener((msg) => this.onMessage(msg)); + this.port.onMessage.addListener((message: any) => { + if (message.command === 'connected') { + this.connected = true; + this.connecting = false; + resolve(); + } else if (message.command === 'disconnected') { + if (this.connecting) { + this.messagingService.send('showDialog', { + text: this.i18nService.t('startDesktopDesc'), + title: this.i18nService.t('startDesktopTitle'), + confirmText: this.i18nService.t('ok'), + type: 'error', + }); + reject(); + } + this.connected = false; + this.port.disconnect(); + return; + } - this.port.onDisconnect.addListener(() => { - this.connected = false; + this.onMessage(message); + }); + + this.port.onDisconnect.addListener(() => { + if (BrowserApi.runtimeLastError().message === 'Specified native messaging host not found.') { + this.messagingService.send('showDialog', { + text: this.i18nService.t('desktopIntegrationDisabledDesc'), + title: this.i18nService.t('desktopIntegrationDisabledTitle'), + confirmText: this.i18nService.t('ok'), + type: 'error', + }); + } + this.connected = false; + reject(); + }); }); } async send(message: any) { // If not connected, try to connect if (!this.connected) { - this.connect(); + await this.connect(); } if (this.sharedSecret == null) { @@ -118,7 +151,7 @@ export class NativeMessagingBackground { const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' '); this.messagingService.send('showDialog', { - html: `${this.i18nService.t('desktopIntegrationVerificationText')}

${fingerprint}.`, + html: `${this.i18nService.t('desktopIntegrationVerificationText')}

${fingerprint}`, title: this.i18nService.t('desktopSyncVerificationTitle'), confirmText: this.i18nService.t('ok'), type: 'warning', @@ -129,7 +162,7 @@ export class NativeMessagingBackground { private async sendUnencrypted(message: any) { if (!this.connected) { - this.connect(); + await this.connect(); } message.timestamp = Date.now(); diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 1ee54a45b18..660defcca33 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -229,4 +229,12 @@ export class BrowserApi { return chrome.runtime.connectNative(application); } } + + static runtimeLastError(): browser.runtime._LastError | chrome.runtime.LastError { + if (BrowserApi.isWebExtensionsApi) { + return browser.runtime.lastError; + } else if (BrowserApi.isChromeApi) { + return chrome.runtime.lastError; + } + } } From 0a4d59092b5b88bd54697a2028bc6d94e888a5ab Mon Sep 17 00:00:00 2001 From: Hinton Date: Wed, 21 Oct 2020 17:18:04 +0200 Subject: [PATCH 13/21] Cleanup, localize error. --- src/_locales/en/messages.json | 8 +- src/background/main.background.ts | 2 +- src/background/nativeMessaging.background.ts | 80 ++++++++++---------- src/popup/accounts/lock.component.ts | 2 +- src/popup/settings/settings.component.html | 2 +- src/popup/settings/settings.component.ts | 3 +- 6 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index ec562879dfe..a2f748cae72 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1383,5 +1383,11 @@ }, "startDesktopDesc": { "message": "The bitwarden desktop application needs to be started before this function can be used." - } + }, + "errorEnableBiometricTitle": { + "message": "Unable to enable biometrics" + }, + "errorEnableBiometricDesc": { + "message": "Action was canceld by the desktop applicaiton." + } } diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 29651d2fcd4..96c4462d89f 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -151,7 +151,7 @@ export default class MainBackground { }, () => { if (this.nativeMessagingBackground != null) { - const promise = this.nativeMessagingBackground.await(); + const promise = this.nativeMessagingBackground.getResponse(); this.nativeMessagingBackground.send({command: 'biometricUnlock'}) return promise.then((result) => result.response === 'unlocked'); } diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 69389358702..d15f3978c89 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -20,7 +20,6 @@ export class NativeMessagingBackground { private port: browser.runtime.Port | chrome.runtime.Port; private resolver: any = null; - private publicKey: ArrayBuffer; private privateKey: ArrayBuffer = null; private secureSetupResolve: any = null; private sharedSecret: SymmetricCryptoKey; @@ -36,27 +35,36 @@ export class NativeMessagingBackground { this.connecting = true; - this.port.onMessage.addListener((message: any) => { - if (message.command === 'connected') { - this.connected = true; - this.connecting = false; - resolve(); - } else if (message.command === 'disconnected') { - if (this.connecting) { - this.messagingService.send('showDialog', { - text: this.i18nService.t('startDesktopDesc'), - title: this.i18nService.t('startDesktopTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - reject(); - } - this.connected = false; - this.port.disconnect(); - return; - } + this.port.onMessage.addListener(async (message: any) => { + switch (message.command) { + case 'connected': + this.connected = true; + this.connecting = false; + resolve(); + break; + case 'disconnected': + if (this.connecting) { + this.messagingService.send('showDialog', { + text: this.i18nService.t('startDesktopDesc'), + title: this.i18nService.t('startDesktopTitle'), + confirmText: this.i18nService.t('ok'), + type: 'error', + }); + reject(); + } + this.connected = false; + this.port.disconnect(); + break; + case 'setupEncryption': + const encrypted = Utils.fromB64ToArray(message.sharedSecret); + const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm); - this.onMessage(message); + this.sharedSecret = new SymmetricCryptoKey(decrypted); + this.secureSetupResolve(); + break; + default: + this.onMessage(message); + } }); this.port.onDisconnect.addListener(() => { @@ -75,7 +83,6 @@ export class NativeMessagingBackground { } async send(message: any) { - // If not connected, try to connect if (!this.connected) { await this.connect(); } @@ -90,21 +97,13 @@ export class NativeMessagingBackground { this.port.postMessage(encrypted); } - await(): Promise { + getResponse(): Promise { return new Promise((resolve, reject) => { this.resolver = resolve; }); } private async onMessage(rawMessage: any) { - if (rawMessage.command === 'setupEncryption') { - const encrypted = Utils.fromB64ToArray(rawMessage.sharedSecret); - const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm); - - this.sharedSecret = new SymmetricCryptoKey(decrypted); - this.secureSetupResolve(); - return; - } const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret)); if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { @@ -122,11 +121,15 @@ export class NativeMessagingBackground { if (message.response === 'unlocked') { await this.storageService.save(ConstantsService.biometricUnlockKey, true); } - - await this.cryptoService.toggleKey(); + break; } - if (this.vaultTimeoutService.biometricLocked) { + // Ignore unlock if already unlockeded + if (!this.vaultTimeoutService.biometricLocked) { + break; + } + + if (message.response === 'unlocked') { this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)); this.vaultTimeoutService.biometricLocked = false; this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); @@ -143,12 +146,11 @@ export class NativeMessagingBackground { } private async secureCommunication() { - // Using crypto function service directly since we cannot encrypt the private key as - // master key might not be available - [this.publicKey, this.privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); + const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); + this.privateKey = privateKey; - this.sendUnencrypted({command: 'setupEncryption', publicKey: Utils.fromBufferToB64(this.publicKey)}); - const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' '); + this.sendUnencrypted({command: 'setupEncryption', publicKey: Utils.fromBufferToB64(publicKey)}); + const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), publicKey)).join(' '); this.messagingService.send('showDialog', { html: `${this.i18nService.t('desktopIntegrationVerificationText')}

${fingerprint}`, diff --git a/src/popup/accounts/lock.component.ts b/src/popup/accounts/lock.component.ts index f7c6729219f..3920e6bcf22 100644 --- a/src/popup/accounts/lock.component.ts +++ b/src/popup/accounts/lock.component.ts @@ -46,7 +46,7 @@ export class LockComponent extends BaseLockComponent { const div = document.createElement('div'); div.innerHTML = `
${this.i18nService.t('awaitDesktop')}
`; - const submitted = Swal.fire({ + Swal.fire({ heightAuto: false, buttonsStyling: false, html: div, diff --git a/src/popup/settings/settings.component.html b/src/popup/settings/settings.component.html index 6439d6e42af..9392db801dc 100644 --- a/src/popup/settings/settings.component.html +++ b/src/popup/settings/settings.component.html @@ -44,7 +44,7 @@
- +
diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index 1029fe29f13..57a65cce77a 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -209,7 +209,6 @@ export class SettingsComponent implements OnInit { async updateBiometric() { if (this.biometric) { this.biometric = false; - // TODO: Remove biometric stuff await this.storageService.remove(ConstantsService.biometricUnlockKey); this.vaultTimeoutService.biometricLocked = false; } else { @@ -241,7 +240,7 @@ export class SettingsComponent implements OnInit { Swal.close(); if (this.biometric === false) { - this.platformUtilsService.showToast('error', 'Unable to enable biometrics', 'Ensure the desktop application is running, and browser integration is enabled.'); + this.platformUtilsService.showToast('error', this.i18nService.t('errorEnableBiometricTitle'), this.i18nService.t('errorEnableBiometricDesc')); } }) ]); From 251d0fdde3a362db0cd52a24e471e1c9f88c6d69 Mon Sep 17 00:00:00 2001 From: Hinton Date: Wed, 21 Oct 2020 17:50:27 +0200 Subject: [PATCH 14/21] Handle another error message --- src/background/nativeMessaging.background.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index d15f3978c89..a03554da273 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -68,7 +68,8 @@ export class NativeMessagingBackground { }); this.port.onDisconnect.addListener(() => { - if (BrowserApi.runtimeLastError().message === 'Specified native messaging host not found.') { + const error = BrowserApi.runtimeLastError().message; + if (error === 'Specified native messaging host not found.' || error === 'Access to the specified native messaging host is forbidden.') { this.messagingService.send('showDialog', { text: this.i18nService.t('desktopIntegrationDisabledDesc'), title: this.i18nService.t('desktopIntegrationDisabledTitle'), From 222665dd9d4e0aec3b94fa831b0bbc597571d117 Mon Sep 17 00:00:00 2001 From: Hinton Date: Wed, 21 Oct 2020 19:23:27 +0200 Subject: [PATCH 15/21] Fix error in firefox --- src/background/nativeMessaging.background.ts | 12 +++++++++--- src/browser/browserApi.ts | 8 -------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index a03554da273..562fd21b31a 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -67,9 +67,15 @@ export class NativeMessagingBackground { } }); - this.port.onDisconnect.addListener(() => { - const error = BrowserApi.runtimeLastError().message; - if (error === 'Specified native messaging host not found.' || error === 'Access to the specified native messaging host is forbidden.') { + this.port.onDisconnect.addListener((p: any) => { + let error; + if (BrowserApi.isWebExtensionsApi) { + error = p.error.message; + } else { + error = chrome.runtime.lastError.message; + } + + if (error === 'Specified native messaging host not found.' || error === 'Access to the specified native messaging host is forbidden.' || error === 'An unexpected error occurred') { this.messagingService.send('showDialog', { text: this.i18nService.t('desktopIntegrationDisabledDesc'), title: this.i18nService.t('desktopIntegrationDisabledTitle'), diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 660defcca33..1ee54a45b18 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -229,12 +229,4 @@ export class BrowserApi { return chrome.runtime.connectNative(application); } } - - static runtimeLastError(): browser.runtime._LastError | chrome.runtime.LastError { - if (BrowserApi.isWebExtensionsApi) { - return browser.runtime.lastError; - } else if (BrowserApi.isChromeApi) { - return chrome.runtime.lastError; - } - } } From b0c9054f231e89c8ba481432bf2ed55adbc35c97 Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 23 Oct 2020 10:42:58 +0200 Subject: [PATCH 16/21] Update with support for latest jslib --- src/services/browserPlatformUtils.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index e0439f6bcb9..dea8ccc7c96 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -307,6 +307,10 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService return null; } + supportsSecureStorage(): boolean { + return false; + } + private isSafariExtension(): boolean { return (window as any).safariAppExtension === true; } From c1b099f5dabe73272f18e16b6ff184d71cfcd4dd Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 23 Oct 2020 14:40:50 +0200 Subject: [PATCH 17/21] Handle invalidatedEncrytption message --- src/_locales/en/messages.json | 8 +++++++- src/background/nativeMessaging.background.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index a2f748cae72..028715b0542 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1389,5 +1389,11 @@ }, "errorEnableBiometricDesc": { "message": "Action was canceld by the desktop applicaiton." - } + }, + "nativeMessagingInvalidEncryptionDesc": { + "message": "Desktop application invalidated the secure communication channel. Please retry this operation" + }, + "nativeMessagingInvalidEncryptionTitle": { + "message": "Desktop communication interupted" + } } diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 562fd21b31a..92cb691bdb7 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -62,6 +62,17 @@ export class NativeMessagingBackground { this.sharedSecret = new SymmetricCryptoKey(decrypted); this.secureSetupResolve(); break; + case 'invalidateEncryption': + this.sharedSecret = null; + this.privateKey = null; + this.connected = false; + + this.messagingService.send('showDialog', { + text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'), + title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'), + confirmText: this.i18nService.t('ok'), + type: 'error', + }); default: this.onMessage(message); } @@ -83,6 +94,8 @@ export class NativeMessagingBackground { type: 'error', }); } + this.sharedSecret = null; + this.privateKey = null; this.connected = false; reject(); }); From 6257764c375da1a2e65717c40aab558f12660245 Mon Sep 17 00:00:00 2001 From: Hinton Date: Thu, 19 Nov 2020 19:16:29 +0100 Subject: [PATCH 18/21] Resolve review comments --- src/_locales/en/messages.json | 14 +++++++------- src/background/nativeMessaging.background.ts | 8 +++++--- src/popup/accounts/lock.component.html | 2 +- src/popup/settings/settings.component.html | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index e0147361287..65c3ed7464d 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1250,14 +1250,14 @@ "yourVaultIsLockedPinCode": { "message": "Your vault is locked. Verify your PIN code to continue." }, - "unlockWithBiometric": { - "message": "Unlock with biometric" + "unlockWithBiometrics": { + "message": "Unlock with biometrics" }, "awaitDesktop": { "message": "Awaiting confirmation from desktop" }, "awaitDesktopDesc": { - "message": "Please confirm using biometrics in the Bitwarden desktop application to enable biometrics for browser." + "message": "Please confirm using biometrics in the Bitwarden Desktop application to enable biometrics for browser." }, "lockWithMasterPassOnRestart": { "message": "Lock with master password on browser restart" @@ -1391,19 +1391,19 @@ "message": "Browser integration is not enabled" }, "desktopIntegrationDisabledDesc": { - "message": "Browser integration is not enabled in the Bitwarden Desktop Application. Please enable it in the settings within the desktop application." + "message": "Browser integration is not enabled in the Bitwarden Desktop application. Please enable it in the settings within the desktop application." }, "startDesktopTitle": { - "message": "Start the Bitwarden Desktop Application" + "message": "Start the Bitwarden Desktop application" }, "startDesktopDesc": { - "message": "The bitwarden desktop application needs to be started before this function can be used." + "message": "The Bitwarden Desktop application needs to be started before this function can be used." }, "errorEnableBiometricTitle": { "message": "Unable to enable biometrics" }, "errorEnableBiometricDesc": { - "message": "Action was canceld by the desktop applicaiton." + "message": "Action was canceled by the desktop application" }, "nativeMessagingInvalidEncryptionDesc": { "message": "Desktop application invalidated the secure communication channel. Please retry this operation" diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 92cb691bdb7..6e572d93e0f 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -66,7 +66,7 @@ export class NativeMessagingBackground { this.sharedSecret = null; this.privateKey = null; this.connected = false; - + this.messagingService.send('showDialog', { text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'), title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'), @@ -86,7 +86,9 @@ export class NativeMessagingBackground { error = chrome.runtime.lastError.message; } - if (error === 'Specified native messaging host not found.' || error === 'Access to the specified native messaging host is forbidden.' || error === 'An unexpected error occurred') { + if (error === 'Specified native messaging host not found.' || + error === 'Access to the specified native messaging host is forbidden.' || + error === 'An unexpected error occurred') { this.messagingService.send('showDialog', { text: this.i18nService.t('desktopIntegrationDisabledDesc'), title: this.i18nService.t('desktopIntegrationDisabledTitle'), @@ -157,7 +159,7 @@ export class NativeMessagingBackground { break; default: // tslint:disable-next-line - console.error('NativeMessage, got unknown command.'); + console.error('NativeMessage, got unknown command: ', message.command); } if (this.resolver) { diff --git a/src/popup/accounts/lock.component.html b/src/popup/accounts/lock.component.html index 773cc20d493..1a40769500b 100644 --- a/src/popup/accounts/lock.component.html +++ b/src/popup/accounts/lock.component.html @@ -38,7 +38,7 @@

diff --git a/src/popup/settings/settings.component.html b/src/popup/settings/settings.component.html index 9392db801dc..42f3287792a 100644 --- a/src/popup/settings/settings.component.html +++ b/src/popup/settings/settings.component.html @@ -43,7 +43,7 @@

- +
Date: Thu, 19 Nov 2020 19:19:31 +0100 Subject: [PATCH 19/21] Update jslib --- jslib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jslib b/jslib index f44e99d74dc..9e4d000b4d0 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit f44e99d74dc011c026525d171f7d2940b60b6587 +Subproject commit 9e4d000b4d02109f8ad3a870327b8dcbb12eb176 From 0e2432d6f40f38c86921c8d0b1db220305c884d0 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 23 Nov 2020 14:27:36 +0100 Subject: [PATCH 20/21] Review comments --- src/background/main.background.ts | 2 +- src/background/nativeMessaging.background.ts | 14 ++++++++------ src/services/browserPlatformUtils.service.ts | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 47b7d5e76b0..fdf2ab81f5a 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -71,6 +71,7 @@ import { SafariApp } from '../browser/safariApp'; import CommandsBackground from './commands.background'; import ContextMenusBackground from './contextMenus.background'; import IdleBackground from './idle.background'; +import { NativeMessagingBackground } from './nativeMessaging.background'; import RuntimeBackground from './runtime.background'; import TabsBackground from './tabs.background'; import WebRequestBackground from './webRequest.background'; @@ -84,7 +85,6 @@ import I18nService from '../services/i18n.service'; import { PopupUtilsService } from '../popup/services/popup-utils.service'; import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service'; -import { NativeMessagingBackground } from './nativeMessaging.background'; export default class MainBackground { messagingService: MessagingServiceAbstraction; diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 6e572d93e0f..88397c05389 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,15 +1,17 @@ -import { CryptoService } from 'jslib/abstractions/crypto.service'; -import { MessagingService } from 'jslib/abstractions/messaging.service'; -import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; +import { ConstantsService } from 'jslib/services/constants.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; import { StorageService } from 'jslib/abstractions/storage.service'; +import { UserService } from 'jslib/abstractions/user.service'; +import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; + import { Utils } from 'jslib/misc/utils'; import { SymmetricCryptoKey } from 'jslib/models/domain'; -import { ConstantsService } from 'jslib/services'; + import { BrowserApi } from '../browser/browserApi'; import RuntimeBackground from './runtime.background'; -import { UserService } from 'jslib/abstractions/user.service'; -import { I18nService } from 'jslib/abstractions/i18n.service'; const MessageValidTimeout = 10 * 1000; const EncryptionAlgorithm = 'sha1'; diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index dea8ccc7c96..b9946937f43 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -1,6 +1,5 @@ import { BrowserApi } from '../browser/browserApi'; import { SafariApp } from '../browser/safariApp'; -import { NativeMessagingBackground } from '../background/nativeMessaging.background'; import { DeviceType } from 'jslib/enums/deviceType'; From 7c468de97cc1a1351227e14fb14397da0a576abd Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 30 Nov 2020 13:41:08 +0100 Subject: [PATCH 21/21] Correctly set biometric state on connect failure --- src/background/main.background.ts | 10 ++++++++-- src/popup/settings/settings.component.html | 2 +- src/popup/settings/settings.component.ts | 10 ++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/background/main.background.ts b/src/background/main.background.ts index fdf2ab81f5a..6de29fa43a2 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -152,10 +152,16 @@ export default class MainBackground { this.systemService.clearClipboard(clipboardValue, clearMs); } }, - () => { + async () => { if (this.nativeMessagingBackground != null) { const promise = this.nativeMessagingBackground.getResponse(); - this.nativeMessagingBackground.send({command: 'biometricUnlock'}) + + try { + await this.nativeMessagingBackground.send({command: 'biometricUnlock'}); + } catch (e) { + return Promise.reject(e); + } + return promise.then((result) => result.response === 'unlocked'); } }); diff --git a/src/popup/settings/settings.component.html b/src/popup/settings/settings.component.html index 42f3287792a..fc1eeedfe79 100644 --- a/src/popup/settings/settings.component.html +++ b/src/popup/settings/settings.component.html @@ -44,7 +44,7 @@
- +
diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index 57a65cce77a..0cf3972dcec 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -208,10 +208,6 @@ export class SettingsComponent implements OnInit { async updateBiometric() { if (this.biometric) { - this.biometric = false; - await this.storageService.remove(ConstantsService.biometricUnlockKey); - this.vaultTimeoutService.biometricLocked = false; - } else { const submitted = Swal.fire({ heightAuto: false, buttonsStyling: false, @@ -242,8 +238,14 @@ export class SettingsComponent implements OnInit { if (this.biometric === false) { this.platformUtilsService.showToast('error', this.i18nService.t('errorEnableBiometricTitle'), this.i18nService.t('errorEnableBiometricDesc')); } + }).catch((e) => { + // Handle connection errors + this.biometric = false; }) ]); + } else { + await this.storageService.remove(ConstantsService.biometricUnlockKey); + this.vaultTimeoutService.biometricLocked = false; } }