From 030257203ccc2963127525c70c318abf2aaf0468 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 13 Dec 2017 11:26:22 +0200 Subject: [PATCH 1/9] Fixed HTTP auth --- keepassxc-browser/background/event.js | 10 ++++++-- keepassxc-browser/background/httpauth.js | 29 ++++++++++++++++++++++-- keepassxc-browser/background/init.js | 17 +------------- keepassxc-browser/background/page.js | 6 ++--- keepassxc-browser/options/options.js | 21 +++++++++++++++-- 5 files changed, 58 insertions(+), 25 deletions(-) diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index 9b42ca3..a2c9f90 100644 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -100,8 +100,8 @@ kpxcEvent.showStatus = function(configured, tab, callback) { }; kpxcEvent.onLoadSettings = function(callback, tab) { - browser.storage.local.get({'settings': {}}).then((item) => { - callback(item.settings); + page.initSettings().then((settings) => { + callback(settings); }, (err) => { console.log('error loading settings: ' + err); }); @@ -230,6 +230,11 @@ kpxcEvent.onLoginPopup = function(callback, tab, logins) { browserAction.show(null, tab); }; +kpxcEvent.initHttpAuth = function(callback) { + httpAuth.init(); + callback(); +} + kpxcEvent.onHTTPAuthPopup = function(callback, tab, data) { let stackData = { level: 1, @@ -274,6 +279,7 @@ kpxcEvent.messageHandlers = { 'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions, 'get_status': kpxcEvent.onGetStatus, 'get_tab_information': kpxcEvent.onGetTabInformation, + 'init_http_auth': kpxcEvent.initHttpAuth, 'load_keyring': kpxcEvent.onLoadKeyRing, 'load_settings': kpxcEvent.onLoadSettings, 'page_clear_logins': kpxcEvent.pageClearLogins, diff --git a/keepassxc-browser/background/httpauth.js b/keepassxc-browser/background/httpauth.js index f74277d..7a53fdd 100644 --- a/keepassxc-browser/background/httpauth.js +++ b/keepassxc-browser/background/httpauth.js @@ -3,6 +3,31 @@ const httpAuth = {}; httpAuth.requests = []; httpAuth.pendingCallbacks = []; +httpAuth.init = function() { + let handleReq = httpAuth.handleRequestPromise; + let reqType = 'blocking'; + + if (!isFirefox()) { + handleReq = httpAuth.handleRequestCallback; + reqType = 'asyncBlocking'; + } + + if (browser.webRequest.onAuthRequired.hasListener(handleReq)) { + browser.webRequest.onAuthRequired.removeListener(handleReq); + browser.webRequest.onCompleted.removeListener(httpAuth.requestCompleted); + browser.webRequest.onErrorOccurred.removeListener(httpAuth.requestCompleted); + } + + // only intercept http auth requests if the option is turned on. + if (page.settings.autoFillAndSend) { + const opts = { urls: [''] }; + + browser.webRequest.onAuthRequired.addListener(handleReq, opts, [reqType]); + browser.webRequest.onCompleted.addListener(httpAuth.requestCompleted, opts); + browser.webRequest.onErrorOccurred.addListener(httpAuth.requestCompleted, opts); + } +}; + httpAuth.requestCompleted = function(details) { let index = httpAuth.requests.indexOf(details.requestId); if (index >= 0) { @@ -41,8 +66,8 @@ httpAuth.processPendingCallbacks = function(details, resolve, reject) { httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) { // at least one login found --> use first to login - if (logins.length > 0) { - if (logins.length == 1 && page.settings.autoFillAndSend) { + if (logins.length > 0 && page.settings.autoFillAndSend) { + if (logins.length === 1) { resolve({ authCredentials: { username: logins[0].login, diff --git a/keepassxc-browser/background/init.js b/keepassxc-browser/background/init.js index 149328a..6655baf 100644 --- a/keepassxc-browser/background/init.js +++ b/keepassxc-browser/background/init.js @@ -1,6 +1,7 @@ keepass.migrateKeyRing().then(() => { page.initSettings().then(() => { page.initOpenedTabs().then(() => { + httpAuth.init(); keepass.connectToNative(); keepass.generateNewKeyPair(); keepass.changePublicKeys(null).then((pkRes) => { @@ -73,22 +74,6 @@ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { } }); -// Retrieve Credentials and try auto-login for HTTPAuth requests -if (browser.webRequest.onAuthRequired) { - let handleReq = httpAuth.handleRequestPromise; - let reqType = 'blocking'; - let opts = { urls: [''] }; - - if (!isFirefox()) { - handleReq = httpAuth.handleRequestCallback; - reqType = 'asyncBlocking'; - } - - browser.webRequest.onAuthRequired.addListener(handleReq, opts, [reqType]); - browser.webRequest.onCompleted.addListener(httpAuth.requestCompleted, opts); - browser.webRequest.onErrorOccurred.addListener(httpAuth.requestCompleted, opts); -} - browser.runtime.onMessage.addListener(kpxcEvent.onMessage); const contextMenuItems = [ diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 8a9d5c2..a806ed1 100644 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -14,8 +14,8 @@ page.blockedTabs = {}; page.initSettings = function() { return new Promise((resolve, reject) => { - kpxcEvent.onLoadSettings((settings) => { - page.settings = settings; + browser.storage.local.get({'settings': {}}).then((item) => { + page.settings = item.settings; if (!('checkUpdateKeePassXC' in page.settings)) { page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC; } @@ -35,7 +35,7 @@ page.initSettings = function() { page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials; } browser.storage.local.set({'settings': page.settings}); - resolve(); + resolve(page.settings); }); }); }; diff --git a/keepassxc-browser/options/options.js b/keepassxc-browser/options/options.js index 47b29d4..f0b275b 100644 --- a/keepassxc-browser/options/options.js +++ b/keepassxc-browser/options/options.js @@ -30,6 +30,18 @@ options.initMenu = function() { $('div.tab:first').show(); }; +options.saveSettingsPromise = function() { + return new Promise((resolve, reject) => { + browser.storage.local.set({'settings': options.settings}).then((item) => { + browser.runtime.sendMessage({ + action: 'load_settings' + }).then((settings) => { + resolve(settings); + }); + }); + }); +} + options.saveSetting = function(name) { const $id = '#' + name; $($id).closest('.control-group').removeClass('error').addClass('success'); @@ -61,8 +73,13 @@ options.initGeneralSettings = function() { }); $('#tab-general-settings input[type=checkbox]').change(function() { - options.settings[$(this).attr('name')] = $(this).is(':checked'); - options.saveSettings(); + const name = $(this).attr('name'); + options.settings[name] = $(this).is(':checked'); + options.saveSettingsPromise().then((x) => { + if (name === 'autoFillAndSend') { + browser.runtime.sendMessage({action: 'init_http_auth'}); + } + }); }); $('#tab-general-settings input[type=radio]').each(function() { From 1f9b76178bc449858009b60076575a5defa20cb8 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 13 Dec 2017 12:48:03 +0200 Subject: [PATCH 2/9] Support for OTP codes via context menu --- keepassxc-browser/background/init.js | 1 + keepassxc-browser/keepassxc-browser.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/keepassxc-browser/background/init.js b/keepassxc-browser/background/init.js index 6655baf..4279020 100644 --- a/keepassxc-browser/background/init.js +++ b/keepassxc-browser/background/init.js @@ -79,6 +79,7 @@ browser.runtime.onMessage.addListener(kpxcEvent.onMessage); const contextMenuItems = [ {title: 'Fill User + Pass', action: 'fill_user_pass'}, {title: 'Fill Pass Only', action: 'fill_pass_only'}, + {title: 'Fill TOTP', action: 'fill_totp'}, {title: 'Show Password Generator Icons', action: 'activate_password_generator'}, {title: 'Save credentials', action: 'remember_credentials'} ]; diff --git a/keepassxc-browser/keepassxc-browser.js b/keepassxc-browser/keepassxc-browser.js index e74d3e5..2d00087 100644 --- a/keepassxc-browser/keepassxc-browser.js +++ b/keepassxc-browser/keepassxc-browser.js @@ -38,6 +38,10 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) { cip.receiveCredentialsIfNecessary(); cip.fillInFromActiveElementPassOnly(false); } + else if (req.action === 'fill_totp') { + cip.receiveCredentialsIfNecessary(); + cip.fillInFromActiveElementTOTPOnly(false); + } else if (req.action === 'activate_password_generator') { cip.initPasswordGenerator(cipFields.getAllFields()); } @@ -1445,6 +1449,26 @@ cip.fillInFromActiveElementPassOnly = function(suppressWarnings) { cip.fillInCredentials(combination, true, suppressWarnings); }; +cip.fillInFromActiveElementTOTPOnly = function(suppressWarnings) { + const el = document.activeElement; + cipFields.setUniqueId(jQuery(el)); + const fieldId = cipFields.prepareId(jQuery(el).attr('data-cip-id')); + + + if (cip.credentials[0]) { + const $sf = _fs(fieldId); + if (cip.credentials[0].stringFields && cip.credentials[0].stringFields.length > 0) { + const sFields = cip.credentials[0].stringFields; + for (const s of sFields) { + const val = s["KPH: {TOTP}"]; + if (val) { + cip.setValue($sf, val); + } + } + } + } +}; + cip.setValue = function(field, value) { if (field.is('select')) { value = value.toLowerCase().trim(); From 66db5486ba0f605ef66c748cba1dcb323d242081 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 13 Dec 2017 12:51:20 +0200 Subject: [PATCH 3/9] README update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 058538e..3e2c55d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This can cause unsaved changes not to be saved. If you use this method it's impo 2. keepassxc-browser communicated with KeePassXC through keepassxc-proxy. The proxy handles listening stdin/stdout and transfers these messages through Unix domain sockets / named pipes to KeePassXC. This means KeePassXC can be used and started normally without inteference from Native Messaging API. keepassxc-browser starts only the proxy application and there's no risk of shutting down KeePassXC or losing any unsaved changes. You don't need to install keepassxc-proxy separately. It is included in the latest KeePassXC fork. Use it if you want to make your own proxy or improve/extend it. Alternatively you can use -[keepassxc-proxy-rust](https://github.com/varjolintu/keepassxc-proxy-rust) as a proxy if you prefer a non-Qt solution. There's also Python and C++ versions available at +[keepassxc-proxy-rust](https://github.com/varjolintu/keepassxc-proxy-rust) as a proxy if you prefer a non-Qt solution. There's also Python and C++ versions available at [keepassxc-proxy](https://github.com/varjolintu/keepassxc-proxy). ## Improvements @@ -36,6 +36,7 @@ The following improvements and features have been made after the fork. At this p - Redesigned password generator dialog - Password generator supports diceware passphrases and extended ASCII characters - Autocomplete works also when only password fields are visible +- Supports TOTP with custom KHP placeholders ## Protocol From fc6106638b0398e215ab2a182ba53357e476fe2c Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 13 Dec 2017 19:12:22 +0200 Subject: [PATCH 4/9] Small updates --- CHANGELOG | 5 +++++ keepassxc-browser/background/keepass.js | 12 +++++++++--- keepassxc-browser/manifest.json | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5f83946..cdb1ca3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +0.4.4 (??-??-2017) +========================= +- Added support for OTP codes via context menu +- Fixed HTTP auth + 0.4.3 (9-12-2017) ========================= - Create password generator dialog only when clicking the icon diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index ae435ac..b924fe7 100644 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -131,7 +131,9 @@ keepass.addCredentials = function(callback, tab, username, password, url) { keepass.updateCredentials = function(callback, tab, entryId, username, password, url) { page.debug('keepass.updateCredentials(callback, {1}, {2}, {3}, [password], {4})', tab.id, entryId, username, url); - page.tabs[tab.id].errorMessage = null; + if (tab && page.tabs[tab.id]) { + page.tabs[tab.id].errorMessage = null; + } keepass.testAssociation((response) => { if (!response) { @@ -200,7 +202,9 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb return; } - page.tabs[tab.id].errorMessage = null; + if (tab && page.tabs[tab.id]) { + page.tabs[tab.id].errorMessage = null; + } if (!keepass.isConnected) { callback([]); @@ -344,7 +348,9 @@ keepass.associate = function(callback, tab) { return; } - page.tabs[tab.id].errorMessage = null; + if (tab && page.tabs[tab.id]) { + page.tabs[tab.id].errorMessage = null; + } const kpAction = kpActions.ASSOCIATE; const key = nacl.util.encodeBase64(keepass.keyPair.publicKey); diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json index e43a67e..be1c515 100644 --- a/keepassxc-browser/manifest.json +++ b/keepassxc-browser/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "keepassxc-browser", - "version": "0.4.3", + "version": "0.4.4", "description": "KeePassXC integration for modern web browsers", "author": "Sami Vänttinen", "icons": { From f7ebd341e27ecfa5adaeddf7e8b725a544d21072 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Fri, 15 Dec 2017 07:12:21 +0200 Subject: [PATCH 5/9] Updated some old protocol text --- keepassxc-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keepassxc-protocol.md b/keepassxc-protocol.md index 8ac2107..48ad101 100644 --- a/keepassxc-protocol.md +++ b/keepassxc-protocol.md @@ -13,7 +13,7 @@ Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweet Encrypted messages are built with these JSON parameters: - action - `test-associate`, `associate`, `get-logins`, `get-logins-count`, `set-login`... - message - Encrypted message, base64 encoded -- nonce - 24 bytes long random data, base64 encoded. This must be the same when responding to a request. +- nonce - 24 bytes long random data, base64 encoded. This is incremented to the response. - clientID - 24 bytes long random data, base64 encoded. This is used to identify different browsers if multiple are used with proxy application. Currently these messages are implemented: From d9809b3e5cb7fd9570a92a8809392986b780ffe6 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Fri, 15 Dec 2017 07:24:14 +0200 Subject: [PATCH 6/9] Updated some old protocol text --- keepassxc-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keepassxc-protocol.md b/keepassxc-protocol.md index 48ad101..46ff01a 100644 --- a/keepassxc-protocol.md +++ b/keepassxc-protocol.md @@ -8,7 +8,7 @@ Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweet 3. All messages between the browser extension and KeePassXC are now encrypted. 4. When keepassxc-browser sends a message it is encrypted with KeePassXC's public key, a random generated nonce and keepassxc-browser's secret key. 5. When KeePassXC sends a message it is encrypted with keepassxc-browser's public key and an incremented nonce. -6. Databases are stored based on the current public key used with `associate`. A new key pair for data transfer is generated each time keepassxc-browser is launched. +6. Databases are stored based on the current public key used with `associate`. This public key used for indentification is not used again afterwards. A new key pair for data transfer is generated each time keepassxc-browser is launched. Encrypted messages are built with these JSON parameters: - action - `test-associate`, `associate`, `get-logins`, `get-logins-count`, `set-login`... From 60008dd692d7a8340c04af30f7cdf79134b0d3a1 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sun, 17 Dec 2017 09:23:58 +0200 Subject: [PATCH 7/9] Documentation changes --- README.md | 10 +++++----- keepassxc-browser/options/options.html | 4 ++-- keepassxc-protocol.md | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3e2c55d..ca6c501 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,15 @@ Please thee this [wiki page](hhttps://github.com/varjolintu/keepassxc-browser/wi ## How it works There are two methods which you can use keepassxc-browser to connect to KeePassXC: -1. keepassxc-browser communicates directly with KeePassXC via stdin/stdout. This method launches KeePassXC every time you start the browser and closes when you exit. -This can cause unsaved changes not to be saved. If you use this method it's important to enable `Automatically save after every change` from KeePassXC's preferences. - -2. keepassxc-browser communicated with KeePassXC through keepassxc-proxy. The proxy handles listening stdin/stdout +1. keepassxc-browser communicated with KeePassXC through keepassxc-proxy. The proxy handles listening stdin/stdout and transfers these messages through Unix domain sockets / named pipes to KeePassXC. This means KeePassXC can be used and started normally without inteference from Native Messaging API. keepassxc-browser starts only the proxy application and there's no risk of shutting down KeePassXC or losing any unsaved changes. You don't need to install keepassxc-proxy separately. It is included in the latest KeePassXC fork. Use it if you want to make your own proxy or improve/extend it. Alternatively you can use [keepassxc-proxy-rust](https://github.com/varjolintu/keepassxc-proxy-rust) as a proxy if you prefer a non-Qt solution. There's also Python and C++ versions available at [keepassxc-proxy](https://github.com/varjolintu/keepassxc-proxy). +2. keepassxc-browser communicates directly with KeePassXC via stdin/stdout. Using native messaging directly is a more secure as it ensures the traffic between KeePassXC and keepassxc-browser is direct. This method launches KeePassXC every time you start the browser and closes when you exit. +This can cause unsaved changes not to be saved. If you use this method it's important to enable `Automatically save after every change` from KeePassXC's preferences. Because this option is not preferred as default it's good to test this feature with your OS and ensure KeePassXC asks to confirm any unsaved changes before exit. + ## Improvements The following improvements and features have been made after the fork. At this point some features are only available with the KeePassXC fork: - Real-time detection of database status (locked/unlocked) @@ -36,7 +36,7 @@ The following improvements and features have been made after the fork. At this p - Redesigned password generator dialog - Password generator supports diceware passphrases and extended ASCII characters - Autocomplete works also when only password fields are visible -- Supports TOTP with custom KHP placeholders +- Supports TOTP with custom KHP placeholders (`KPH: {TOPT}`) ## Protocol diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 1a24336..81c57da 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -200,10 +200,10 @@