diff --git a/CHANGELOG b/CHANGELOG index 9096398..58e4045 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,31 @@ -0.3.2 (2017-09-30) +0.3.6 (20-10-2017) +========================= +- Restricted page credentials polling to active tab + +0.3.5 (19-10-2017) +========================= +- Removed database locked/unlocked status polling and replaced it with message handling from KeePassXC signals +- Clear or change page credentials if a database is changed or locked + +0.3.4 (14-10-2017) +========================= +- Added support for Lock Database button +- Fixed some error message handling + +0.3.3 (12-10-2017) +========================= +- Fixed database reloading when KeePassXC has restarted and database is opened +- New buttons with glyphicons + +0.3.2 (30-09-2017) ========================= - Improved timeout handling -0.3.1 (2017-09-29) +0.3.1 (29-09-2017) ========================= - Added timeout handling for postMessage(). Allows proxy application to be reloaded. -0.3.0 (2017-09-18) +0.3.0 (18-09-2017) ========================= - Added Mozilla's browser-polyfill for making WebExtension compatibility easier - Merged changes from the latest passifox (credits to smorks/keepasshttp-connector) @@ -15,17 +34,17 @@ - Automatic detection of div's with forms that are non-hidden by user interaction - Verified the source code via JSHint -0.2.9 (2017-08-27) +0.2.9 (27-08-2017) ========================= - Code cleaning, global functions moved to global.js - New popup state and button when database is closed or locked - Fixed HTTP auth login with Chrome/Chromium/Vivaldi -0.2.8 (2017-08-08) +0.2.8 (08-08-2017) ========================= - Changed Firefox's minimum version to 55.0 -0.2.7 (2017-07-31) +0.2.7 (31-07-2017) ========================= - Some Firefox related changes (credits to projectgus) - Fixed Skip button function when choosing own credential fields @@ -34,17 +53,17 @@ - Added null checking for onDisconnected() - Any Chrome related stuff is now disabled on options pages when using Firefox -0.2.6 (2017-07-23) +0.2.6 (23-07-2017) ========================= - Fixed error message variables -0.2.5 (2017-07-21) +0.2.5 (21-07-2017) ========================= - Fixed incorrect return value in keepass.getCryptoKey - Added a better error message handling - Added an error message when Native Messaging is disabled in KeePassXC -0.2.4 (2017-07-11) +0.2.4 (11-07-2017) ========================= - Changed comparison operators to strict ones (and some code cleaning) - Copy and Fill & copy buttons are now hidden when Password Generator has an error @@ -52,11 +71,11 @@ - Fix for password generator (error is now shown immediately instead of a blank dialog) - Use a single password generator icon -0.2.3 (2017-07-05) +0.2.3 (05-07-2017) ========================= - Fixed a few variables -0.2.2 (2017-07-04) +0.2.2 (04-07-2017) ========================= - Some code cleaning and rewriting - Fixed displaying 'Database not opened' error message @@ -64,74 +83,74 @@ - Added support for UDP port selector for proxy applications - Fixed strict_min_version for Firefox Nightly -0.2.1 (2017-06-27) +0.2.1 (27-06-2017) ========================= - get-databasehash request/response is now encrypted -0.2.0 (2017-06-26) +0.2.0 (26-06-2017) ========================= - Added JSON install files and script for Windows - New version checking function - Added error message handling - Added support for reloading KeePassXC (or proxy) with new public keys -0.1.10 (2017-06-14) +0.1.10 (14-06-2017) ========================= - Updated manifest and json files up to date -0.1.9 (2017-06-14) +0.1.9 (14-06-2017) ========================= - Renamed the project to keepassxc-browser -0.1.8 (2017-06-13) +0.1.8 (13-06-2017) ========================= - Fixed showing wrong status messages - Enable relaunching and reconnecting to KeePassXC (create new keys on the fly) -0.1.7 (2017-05-28) +0.1.7 (28-05-2017) ========================= - Removed debug logging messages - Removed unnecessary permissions from manifest -0.1.6 (2017-05-27) +0.1.6 (27-05-2017) ========================= - Upgraded tweetnacl-js to 1.0.0 - Upgraded tweetnacl-utils-js to 0.15.0 - Some code fixes concerning encryption and decryption - Redesigned simpler password generator dialog -0.1.5 (2017-05-22) +0.1.5 (22-05-2017) ========================= - Fixed a few deprecated functions - Added some more Firefox compatible code (Firefox now works 90%!) - Removed an unncecessary .map file -0.1.4 (2017-05-21) +0.1.4 (21-05-2017) ========================= - Upgraded manifest options to V2 - Added some more Firefox compatible code -0.1.3 (2017-05-19) +0.1.3 (19-05-2017) ========================= - Fixed a bug showing correct status in the popup - Added a license for a quick method to determine which browser is used in API calls -0.1.2 (2017-05-18) +0.1.2 (18-05-2017) ========================= - Upgraded jquery from 3.2.0 to 3.2.1 - Removed unnecessary images - Upgraded deprecated API calls (extension -> runtime, so from synchronous to asynchronous) - Partial Firefox support (the extension can be loaded but functionality is still limited) -0.1.1 (2017-04-28) +0.1.1 (28-04-2017) ========================= - This version works with the KeePassXC fork - Upgraded JavaScripts to work asynchronously -0.1.0 (2017-04-12) +0.1.0 (12-04-2017) ========================= - Replaced crypto libraries with tweetnacl-js - New application and popup icons - Upgraded bootstrap to version 3.3.7 - Upgraded jquery from 1.11 to 3.2.0 -- Upgraded jquery-ui from 1.10.2 to 1.12.1 \ No newline at end of file +- Upgraded jquery-ui from 1.10.2 to 1.12.1 diff --git a/README.md b/README.md index 3972f10..892d330 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,19 @@ and transfers these messages through a localhost UDP port 19700 (configurable) t Native Messaging API. keepassxc-browser starts only the proxy application and there's no risk of shutting down KeePassXC or losing any unsaved changes. keepassxc-proxy is still under development. If you want, you are free to write your own proxy that handles the traffic. +## 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) +- Credentials on a page are cleared or received automatically again if database is locked or changed to another +- It is possible to lock the active database from the popup (using the red lock icon) +- Input forms are detected even if the login div has been hidden or is created after the page was loaded +- It is possible to use the active database from multiple browsers at the same time with [keepassxc-proxy](https://github.com/varjolintu/keepassxc-proxy) application. +- Deprecated JavaScript functions are removed and everything is asynchronous +- Updated Bootstrap to version 3.3.7 and jQuery to version 3.2.1 +- New buttons, icons and settings page graphics +- Redesigned password generator dialog +- Password generator supports diceware passphrases and extended ASCII characters + ## Protocol Transmitting messages between KeePassXC and keepassxc-browser is totally rewritten. This is still under development. @@ -38,6 +51,7 @@ 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. +- clientID - 24 bytes long random data, base64 encoded. This is used to identify different browsers if multiple are used with proxy application. ### change-public-keys Request: @@ -46,7 +60,8 @@ Request: "action": "change-public-keys", "publicKey": "", "proxyPort": "", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" } ``` @@ -61,7 +76,7 @@ Response (success): ``` ### get-databasehash -Request: +Request (unencrypted): ```javascript { "action": "get-databasehash" @@ -91,7 +106,8 @@ Request: { "action": "associate", "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" } ``` @@ -112,7 +128,8 @@ Unencrypted message: { "action": "test-associate", "id": "", - "key": "" + "key": "", + "clientID": "" } ``` @@ -141,7 +158,8 @@ Request: ```javascript { "action": "generate-password", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" } ``` @@ -175,7 +193,8 @@ Request: { "action": "get-logins", "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" } ``` @@ -220,7 +239,8 @@ Request: { "action": "set-login", "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" } ``` @@ -237,6 +257,26 @@ Response message data (success, decrypted): } ``` +### lock-database +Request: +```javascript +{ + "action": "lock-database", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" +} +``` + +Response message data (success always returns an error, decrypted): +```javascript +{ + "action": "lock-database", + "errorCode": 1, + "error": "Database not opened", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" +} +``` + ## Licenses ``` @@ -275,4 +315,4 @@ Feel free to support this project: - Donate via [PayPal](https://paypal.me/varjolintu) - Donate via Bitcoin: 1LHbD69CcmpLW5hjUXs2MGJhw3GxwqLdw3 -Also consider donating to [KeePassXC](https://flattr.com/submit/auto?fid=x7yqz0&url=https%3A%2F%2Fkeepassxc.org) and passifox teams [(1)](https://github.com/smorks/passifox),[(2)](https://github.com/projectgus/passifox),[(3)](https://github.com/pfn/passifox). They are doing great job. \ No newline at end of file +Also consider donating to [KeePassXC](https://flattr.com/submit/auto?fid=x7yqz0&url=https%3A%2F%2Fkeepassxc.org) and passifox teams [(1)](https://github.com/smorks/passifox),[(2)](https://github.com/projectgus/passifox),[(3)](https://github.com/pfn/passifox). They are doing great job. diff --git a/keepassxc-browser/background/browserAction.js b/keepassxc-browser/background/browserAction.js index 12dd782..3a67a12 100644 --- a/keepassxc-browser/background/browserAction.js +++ b/keepassxc-browser/background/browserAction.js @@ -3,8 +3,6 @@ var browserAction = {}; const BLINK_TIMEOUT_DEFAULT = 7500; const BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT = -1; const BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1; -const MAXIMUM_POLL_COUNT = 8; // _interval (250 ms) * 8 = 2 sec -let databasePollCount = 0; browserAction.show = function(callback, tab) { let data = {}; @@ -36,21 +34,6 @@ browserAction.update = function(interval) { let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1]; - // Poll the database if it's open or closed - if (databasePollCount >= MAXIMUM_POLL_COUNT) { - if (keepass.isKeePassXCAvailable) { - keepass.testAssociation((response) => { - keepass.isConfigured((configured) => { - data.iconType = configured ? 'normal' : 'cross'; - browserAction.show(null, {'id': page.currentTabId}); - }); - }, null); - } - databasePollCount = 0; - } else { - databasePollCount += 1; - } - if (typeof data.visibleForMilliSeconds !== 'undefined') { if (data.visibleForMilliSeconds <= 0) { browserAction.stackPop(page.currentTabId); @@ -279,4 +262,4 @@ browserAction.generateIconName = function(iconType, icon) { name += '_19x19.png'; return name; -}; \ No newline at end of file +}; diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index cf2df09..a0de69b 100644 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -1,14 +1,14 @@ -var event = {}; +var kpxcEvent = {}; -event.onMessage = function(request, sender, callback) { - if (request.action in event.messageHandlers) { +kpxcEvent.onMessage = function(request, sender, callback) { + if (request.action in kpxcEvent.messageHandlers) { //console.log('onMessage(' + request.action + ') for #' + sender.tab.id); if (!sender.hasOwnProperty('tab') || sender.tab.id < 1) { sender.tab = {}; sender.tab.id = page.currentTabId; } - event.invoke(event.messageHandlers[request.action], callback, sender.tab.id, request.args); + kpxcEvent.invoke(kpxcEvent.messageHandlers[request.action], callback, sender.tab.id, request.args); // onMessage closes channel for callback automatically // if this method does not return true @@ -29,7 +29,7 @@ event.onMessage = function(request, sender, callback) { * @param {bool} secondTime * @returns null (asynchronous) */ -event.invoke = function(handler, callback, senderTabId, args, secondTime) { +kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) { if (senderTabId < 1) { return; } @@ -51,7 +51,7 @@ event.invoke = function(handler, callback, senderTabId, args, secondTime) { // using window.open() if (!secondTime) { window.setTimeout(function() { - event.invoke(handler, callback, senderTabId, args, true); + kpxcEvent.invoke(handler, callback, senderTabId, args, true); }, 250); } return; @@ -75,12 +75,12 @@ event.invoke = function(handler, callback, senderTabId, args, secondTime) { }); }; -event.onShowAlert = function(callback, tab, message) { +kpxcEvent.onShowAlert = function(callback, tab, message) { if (page.settings.supressAlerts) { console.log(message); } else { alert(message); } }; -event.showStatus = function(configured, tab, callback) { +kpxcEvent.showStatus = function(configured, tab, callback) { let keyId = null; if (configured) { keyId = keepass.keyRing[keepass.databaseHash].id; @@ -99,11 +99,11 @@ event.showStatus = function(configured, tab, callback) { }); }; -event.onLoadSettings = function(callback, tab) { +kpxcEvent.onLoadSettings = function(callback, tab) { page.settings = (typeof(localStorage.settings) === 'undefined') ? {} : JSON.parse(localStorage.settings); }; -event.onLoadKeyRing = function(callback, tab) { +kpxcEvent.onLoadKeyRing = function(callback, tab) { keepass.keyRing = (typeof(localStorage.keyRing) === 'undefined') ? {} : JSON.parse(localStorage.keyRing); if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) { keepass.associated = { @@ -113,36 +113,36 @@ event.onLoadKeyRing = function(callback, tab) { } }; -event.onGetSettings = function(callback, tab) { - event.onLoadSettings(); +kpxcEvent.onGetSettings = function(callback, tab) { + kpxcEvent.onLoadSettings(); callback({ data: page.settings }); }; -event.onSaveSettings = function(callback, tab, settings) { +kpxcEvent.onSaveSettings = function(callback, tab, settings) { localStorage.settings = JSON.stringify(settings); - event.onLoadSettings(); + kpxcEvent.onLoadSettings(); }; -event.onGetStatus = function(callback, tab) { +kpxcEvent.onGetStatus = function(callback, tab) { keepass.testAssociation((response) => { keepass.isConfigured((configured) => { - event.showStatus(configured, tab, callback); + kpxcEvent.showStatus(configured, tab, callback); }); }, tab, true); }; -event.onReconnect = function(callback, tab) { +kpxcEvent.onReconnect = function(callback, tab) { keepass.connectToNative(); // Add a small timeout after reconnecting. Just to make sure. It's not pretty, I know :( - setTimeout(() => { + setTimeout(() => { keepass.generateNewKeyPair(); keepass.changePublicKeys(tab, (pkRes) => { keepass.getDatabaseHash((gdRes) => { if (gdRes) { keepass.testAssociation((response) => { keepass.isConfigured((configured) => { - event.showStatus(configured, tab, callback); + kpxcEvent.showStatus(configured, tab, callback); }); }, tab); } @@ -151,24 +151,30 @@ event.onReconnect = function(callback, tab) { }, 2000); }; -event.onPopStack = function(callback, tab) { +kpxcEvent.lockDatabase = function(callback, tab) { + keepass.lockDatabase((response) => { + kpxcEvent.showStatus(true, tab, callback); + }, tab); +}; + +kpxcEvent.onPopStack = function(callback, tab) { browserAction.stackPop(tab.id); browserAction.show(null, tab); }; -event.onGetTabInformation = function(callback, tab) { +kpxcEvent.onGetTabInformation = function(callback, tab) { const id = tab.id || page.currentTabId; callback(page.tabs[id]); }; -event.onGetConnectedDatabase = function(callback, tab) { +kpxcEvent.onGetConnectedDatabase = function(callback, tab) { callback({ count: Object.keys(keepass.keyRing).length, identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null }); }; -event.onGetKeePassXCVersions = function(callback, tab) { +kpxcEvent.onGetKeePassXCVersions = function(callback, tab) { if (keepass.currentKeePassXC.version === 0) { keepass.getDatabaseHash((response) => { callback({current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version}); @@ -177,25 +183,25 @@ event.onGetKeePassXCVersions = function(callback, tab) { callback({current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version}); }; -event.onCheckUpdateKeePassXC = function(callback, tab) { +kpxcEvent.onCheckUpdateKeePassXC = function(callback, tab) { keepass.checkForNewKeePassXCVersion(); callback({current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version}); }; -event.onUpdateAvailableKeePassXC = function(callback, tab) { +kpxcEvent.onUpdateAvailableKeePassXC = function(callback, tab) { callback(keepass.keePassXCUpdateAvailable()); }; -event.onRemoveCredentialsFromTabInformation = function(callback, tab) { +kpxcEvent.onRemoveCredentialsFromTabInformation = function(callback, tab) { const id = tab.id || page.currentTabId; page.clearCredentials(id); }; -event.onSetRememberPopup = function(callback, tab, username, password, url, usernameExists, credentialsList) { +kpxcEvent.onSetRememberPopup = function(callback, tab, username, password, url, usernameExists, credentialsList) { browserAction.setRememberPopup(tab.id, username, password, url, usernameExists, credentialsList); }; -event.onLoginPopup = function(callback, tab, logins) { +kpxcEvent.onLoginPopup = function(callback, tab, logins) { let stackData = { level: 1, iconType: 'questionmark', @@ -206,7 +212,7 @@ event.onLoginPopup = function(callback, tab, logins) { browserAction.show(null, tab); }; -event.onHTTPAuthPopup = function(callback, tab, data) { +kpxcEvent.onHTTPAuthPopup = function(callback, tab, data) { let stackData = { level: 1, iconType: 'questionmark', @@ -217,7 +223,7 @@ event.onHTTPAuthPopup = function(callback, tab, data) { browserAction.show(null, tab); }; -event.onMultipleFieldsPopup = function(callback, tab) { +kpxcEvent.onMultipleFieldsPopup = function(callback, tab) { let stackData = { level: 1, iconType: 'normal', @@ -227,37 +233,46 @@ event.onMultipleFieldsPopup = function(callback, tab) { browserAction.show(null, tab); }; -event.pageClearLogins = function(callback, tab) { +kpxcEvent.pageClearLogins = function(callback, tab) { page.clearLogins(tab.id); callback(); }; +kpxcEvent.oldDatabaseHash = 'no-hash'; +kpxcEvent.checkDatabaseHash = function(callback, tab) { + keepass.getDatabaseHash((response) => { + callback({old: kpxcEvent.oldDatabaseHash, new: response}); + kpxcEvent.oldDatabaseHash = response; + }); +}; // all methods named in this object have to be declared BEFORE this! -event.messageHandlers = { +kpxcEvent.messageHandlers = { 'add_credentials': keepass.addCredentials, - 'alert': event.onShowAlert, + 'alert': kpxcEvent.onShowAlert, 'associate': keepass.associate, - 'check_update_keepassxc': event.onCheckUpdateKeePassXC, - 'get_connected_database': event.onGetConnectedDatabase, - 'get_keepassxc_versions': event.onGetKeePassXCVersions, - 'get_settings': event.onGetSettings, - 'get_status': event.onGetStatus, - 'get_tab_information': event.onGetTabInformation, - 'load_keyring': event.onLoadKeyRing, - 'load_settings': event.onLoadSettings, - 'page_clear_logins': event.pageClearLogins, - 'pop_stack': event.onPopStack, - 'popup_login': event.onLoginPopup, - 'popup_multiple-fields': event.onMultipleFieldsPopup, - 'remove_credentials_from_tab_information': event.onRemoveCredentialsFromTabInformation, + 'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC, + 'get_connected_database': kpxcEvent.onGetConnectedDatabase, + 'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions, + 'get_settings': kpxcEvent.onGetSettings, + 'get_status': kpxcEvent.onGetStatus, + 'get_tab_information': kpxcEvent.onGetTabInformation, + 'load_keyring': kpxcEvent.onLoadKeyRing, + 'load_settings': kpxcEvent.onLoadSettings, + 'page_clear_logins': kpxcEvent.pageClearLogins, + 'pop_stack': kpxcEvent.onPopStack, + 'popup_login': kpxcEvent.onLoginPopup, + 'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup, + 'remove_credentials_from_tab_information': kpxcEvent.onRemoveCredentialsFromTabInformation, 'retrieve_credentials': keepass.retrieveCredentials, 'show_default_browseraction': browserAction.showDefault, 'update_credentials': keepass.updateCredentials, - 'save_settings': event.onSaveSettings, - 'set_remember_credentials': event.onSetRememberPopup, + 'save_settings': kpxcEvent.onSaveSettings, + 'set_remember_credentials': kpxcEvent.onSetRememberPopup, 'stack_add': browserAction.stackAdd, - 'update_available_keepassxc': event.onUpdateAvailableKeePassXC, + 'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC, 'generate_password': keepass.generatePassword, - 'reconnect': event.onReconnect -}; \ No newline at end of file + 'reconnect': kpxcEvent.onReconnect, + 'lock-database': kpxcEvent.lockDatabase, + 'check_databasehash': kpxcEvent.checkDatabaseHash +}; diff --git a/keepassxc-browser/background/httpauth.js b/keepassxc-browser/background/httpauth.js index 06936d6..f1ad0ba 100644 --- a/keepassxc-browser/background/httpauth.js +++ b/keepassxc-browser/background/httpauth.js @@ -41,7 +41,7 @@ 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) { - event.onHTTPAuthPopup(null, { "id": details.tabId }, { "logins": logins, "url": details.searchUrl }); + kpxcEvent.onHTTPAuthPopup(null, { "id": details.tabId }, { "logins": logins, "url": details.searchUrl }); //generate popup-list for HTTP Auth usernames + descriptions if (page.settings.autoFillAndSend) { diff --git a/keepassxc-browser/background/init.js b/keepassxc-browser/background/init.js index 72b4b9d..6bc33c1 100644 --- a/keepassxc-browser/background/init.js +++ b/keepassxc-browser/background/init.js @@ -22,7 +22,7 @@ browser.tabs.onCreated.addListener((tab) => { //console.log('browser.tabs.onCreated(' + tab.id+ ')'); if (tab.selected) { page.currentTabId = tab.id; - event.invoke(page.switchTab, null, tab.id, []); + kpxcEvent.invoke(page.switchTab, null, tab.id, []); } } }); @@ -53,8 +53,8 @@ browser.tabs.onActivated.addListener((activeInfo) => { if (info && info.id) { page.currentTabId = info.id; if (info.status === 'complete') { - //console.log('event.invoke(page.switchTab, null, '+info.id + ', []);'); - event.invoke(page.switchTab, null, info.id, []); + //console.log('kpxcEvent.invoke(page.switchTab, null, '+info.id + ', []);'); + kpxcEvent.invoke(page.switchTab, null, info.id, []); } } }); @@ -67,7 +67,7 @@ browser.tabs.onActivated.addListener((activeInfo) => { */ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === 'complete') { - event.invoke(browserAction.removeRememberPopup, null, tabId, []); + kpxcEvent.invoke(browserAction.removeRememberPopup, null, tabId, []); } }); @@ -87,7 +87,7 @@ if (browser.webRequest.onAuthRequired) { browser.webRequest.onErrorOccurred.addListener(httpAuth.requestCompleted, opts); } -browser.runtime.onMessage.addListener(event.onMessage); +browser.runtime.onMessage.addListener(kpxcEvent.onMessage); const contextMenuItems = [ {title: 'Fill &User + Pass', action: 'fill_user_pass'}, @@ -131,4 +131,4 @@ browser.commands.onCommand.addListener((command) => { // Interval which updates the browserAction (e.g. blinking icon) window.setInterval(function() { browserAction.update(_interval); -}, _interval); \ No newline at end of file +}, _interval); diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index e5b2d4c..69fb55b 100644 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -1,3 +1,5 @@ +'use strict'; + var keepass = {}; keepass.associated = {'value': false, 'hash': null}; @@ -20,7 +22,7 @@ keepass.databaseHash = 'no-hash'; //no-hash = KeePassXC is too old and does not keepass.keyRing = (typeof(localStorage.keyRing) === 'undefined') ? {} : JSON.parse(localStorage.keyRing); keepass.keyId = 'keepassxc-browser-cryptokey-name'; keepass.keyBody = 'keepassxc-browser-key'; -keepass.messageTimeout = 1000; // milliseconds +keepass.messageTimeout = 500; // milliseconds const kpActions = { SET_LOGIN: 'set-login', @@ -29,22 +31,28 @@ const kpActions = { ASSOCIATE: 'associate', TEST_ASSOCIATE: 'test-associate', GET_DATABASE_HASH: 'get-databasehash', - CHANGE_PUBLIC_KEYS: 'change-public-keys' + CHANGE_PUBLIC_KEYS: 'change-public-keys', + LOCK_DATABASE: 'lock-database', + DATABASE_LOCKED: 'database-locked', + DATABASE_UNLOCKED: 'database-unlocked' }; const kpErrors = { UNKNOWN_ERROR: 0, DATABASE_NOT_OPENED: 1, - DATABASE_HASH_NOT_RECEIVED: 2, - CLIENT_PUBLIC_KEY_NOT_RECEIVED: 3, - CANNOT_DECRYPT_MESSAGE: 4, - TIMEOUT_OR_NOT_CONNECTED: 5, - ACTION_CANCELLED_OR_DENIED: 6, - PUBLIC_KEY_NOT_FOUND: 7, - ASSOCIATION_FAILED: 8, - KEY_CHANGE_FAILED: 9, - ENCRYPTION_KEY_UNRECOGNIZED: 10, - NO_SAVED_DATABASES_FOUND: 11, + DATABASE_HASH_NOT_RECEIVED: 2, + CLIENT_PUBLIC_KEY_NOT_RECEIVED: 3, + CANNOT_DECRYPT_MESSAGE: 4, + TIMEOUT_OR_NOT_CONNECTED: 5, + ACTION_CANCELLED_OR_DENIED: 6, + PUBLIC_KEY_NOT_FOUND: 7, + ASSOCIATION_FAILED: 8, + KEY_CHANGE_FAILED: 9, + ENCRYPTION_KEY_UNRECOGNIZED: 10, + NO_SAVED_DATABASES_FOUND: 11, + INCORRECT_ACTION: 12, + EMPTY_MESSAGE_RECEIVED: 13, + NO_URL_PROVIDED: 14, errorMessages : { 0: { msg: 'Unknown error' }, @@ -58,7 +66,10 @@ const kpErrors = { 8: { msg: 'KeePassXC association failed, try again.' }, 9: { msg: 'Key change was not successful.' }, 10: { msg: 'Encryption key is not recognized' }, - 11: { msg: 'No saved databases found.' } + 11: { msg: 'No saved databases found.' }, + 12: { msg: 'Incorrect action.' }, + 13: { msg: 'Empty message received.' }, + 14: { msg: 'No URL provided.' } }, getError(errorCode) { @@ -117,7 +128,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password, } } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.error, response.errorCode); + keepass.handleError(tab, response.errorCode, response.error); } else { browserAction.showDefault(null, tab); @@ -193,7 +204,7 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb } } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.error, response.errorCode); + keepass.handleError(tab, response.errorCode, response.error); } else { browserAction.showDefault(null, tab); @@ -211,7 +222,7 @@ keepass.callbackOnId = function(ev, action, tab, callback, enableTimeout = false if (msg && msg.action === action) { ev.removeListener(handler); if (enableTimeout) { - clearTimeout(timeout); + clearTimeout(timeout); } callback(msg); } @@ -290,7 +301,7 @@ keepass.generatePassword = function(callback, tab, forceCallback) { } } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.error, response.errorCode); + keepass.handleError(tab, response.errorCode, response.error); } }); keepass.nativePort.postMessage(request); @@ -349,7 +360,7 @@ keepass.associate = function(callback, tab) { } } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.error, response.errorCode); + keepass.handleError(tab, response.errorCode, response.error); } }); keepass.nativePort.postMessage(request); @@ -402,7 +413,7 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) { id: dbid, key: dbkey }; - + const request = { action: kpAction, message: keepass.encrypt(messageData, nonce), @@ -438,7 +449,7 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) { } } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.error, response.errorCode); + keepass.handleError(tab, response.errorCode, response.error); } callback(keepass.isAssociated()); }); @@ -504,21 +515,21 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) { keepass.isDatabaseClosed = true; keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED); callback(keepass.databaseHash); - } + } } } else { keepass.databaseHash = 'no-hash'; keepass.isDatabaseClosed = true; + keepass.isKeePassXCAvailable = false; if (response.message === "") { keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED); } else { keepass.handleError(tab, response.errorCode, response.error); } - //keepass.handleError(tab, response.errorCode, response.error); callback(keepass.databaseHash); - } + } }, enableTimeout); keepass.nativePort.postMessage(request); }; @@ -561,6 +572,50 @@ keepass.changePublicKeys = function(tab, callback) { keepass.nativePort.postMessage(message); }; +keepass.lockDatabase = function(callback, tab, forceCallback) { + if (!keepass.isConnected) { + keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED); + callback([]); + return; + } + + const kpAction = kpActions.LOCK_DATABASE; + const nonce = nacl.randomBytes(keepass.keySize); + + const messageData = { + action: kpAction + }; + + const request = { + action: kpAction, + message: keepass.encrypt(messageData, nonce), + nonce: keepass.b64e(nonce), + clientID: keepass.clientID + }; + + keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => { + if (response.message && response.nonce) { + const res = keepass.decrypt(response.message, response.nonce); + if (res) { + const message = nacl.util.encodeUTF8(res); + const parsed = JSON.parse(message); + keepass.setcurrentKeePassXCVersion(parsed.version); + + if (keepass.verifyResponse(parsed, response.nonce)) { + keepass.isDatabaseClosed = true; + keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED); + callback(false); + } + } + } + else if (response.error && response.errorCode) { + keepass.handleError(tab, response.errorCode, response.error); + } + callback(false); + }); + keepass.nativePort.postMessage(request); +}; + keepass.generateNewKeyPair = function() { keepass.keyPair = nacl.box.keyPair(); //console.log(keepass.b64e(keepass.keyPair.publicKey) + ' ' + keepass.b64e(keepass.keyPair.secretKey)); @@ -691,6 +746,17 @@ keepass.connectToNative = function() { keepass.onNativeMessage = function(response) { //console.log('Received message: ' + JSON.stringify(response)); + + // Handle database lock/unlock status + if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) { + keepass.testAssociation((response) => { + keepass.isConfigured((configured) => { + let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1]; + data.iconType = configured ? 'normal' : 'cross'; + browserAction.show(null, {'id': page.currentTabId}); + }); + }, null); + } }; function onDisconnected() { diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 0a6baba..8ffd51e 100644 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -16,7 +16,7 @@ page.settings = (typeof(localStorage.settings) === 'undefined') ? {} : JSON.pars page.blockedTabs = {}; page.initSettings = function() { - event.onLoadSettings(); + kpxcEvent.onLoadSettings(); if (!('checkUpdateKeePassXC' in page.settings)) { page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC; } diff --git a/keepassxc-browser/fonts/glyphicons-halflings-regular.eot b/keepassxc-browser/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/keepassxc-browser/fonts/glyphicons-halflings-regular.eot differ diff --git a/keepassxc-browser/fonts/glyphicons-halflings-regular.svg b/keepassxc-browser/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..94fb549 --- /dev/null +++ b/keepassxc-browser/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keepassxc-browser/fonts/glyphicons-halflings-regular.ttf b/keepassxc-browser/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/keepassxc-browser/fonts/glyphicons-halflings-regular.ttf differ diff --git a/keepassxc-browser/fonts/glyphicons-halflings-regular.woff b/keepassxc-browser/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/keepassxc-browser/fonts/glyphicons-halflings-regular.woff differ diff --git a/keepassxc-browser/fonts/glyphicons-halflings-regular.woff2 b/keepassxc-browser/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/keepassxc-browser/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/keepassxc-browser/keepassxc-browser.js b/keepassxc-browser/keepassxc-browser.js index 7de7bea..34e2b5c 100644 --- a/keepassxc-browser/keepassxc-browser.js +++ b/keepassxc-browser/keepassxc-browser.js @@ -202,7 +202,7 @@ cipPassword.createDialog = function() { const $dialog = jQuery('
') .addClass('dialog-form') .attr('id', 'cip-genpw-dialog'); - + const $inputDiv = jQuery('
').addClass('form-group'); const $inputGroup = jQuery('
').addClass('genpw-input-group'); const $textfieldPassword = jQuery('') @@ -220,7 +220,7 @@ cipPassword.createDialog = function() { .attr('id', 'cip-genpw-quality') .text('123 Bits'); $inputGroup.append($textfieldPassword).append($quality); - + const $checkGroup = jQuery('
').addClass('genpw-input-group'); const $checkboxNextField = jQuery('') .attr('id', 'cip-genpw-checkbox-next-field') @@ -246,7 +246,7 @@ cipPassword.createDialog = function() { title: 'Password Generator', classes: {'ui-dialog': 'ui-corner-all'}, buttons: { - 'Generate': + 'Generate': { text: 'Generate', id: 'cip-genpw-btn-generate', @@ -266,7 +266,7 @@ cipPassword.createDialog = function() { cipPassword.copyPasswordToClipboard(); } }, - 'Fill & copy': + 'Fill & copy': { text: 'Fill & copy', id: 'cip-genpw-btn-fillin', @@ -1102,6 +1102,7 @@ cip.credentials = []; jQuery(function() { cip.init(); cip.detectNewActiveFields(); + cip.detectDatabaseChange(); }); cip.init = function() { @@ -1128,6 +1129,39 @@ cip.detectNewActiveFields = function() { //} }; +// Switch credentials if database is changed or closed +cip.detectDatabaseChange = function() { + const dbDetectInterval = setInterval(function() { + if (document.visibilityState !== 'hidden') { + browser.runtime.sendMessage({ + action: 'check_databasehash' + }).then((response) => { + if (response.new === 'no-hash') { + cipEvents.clearCredentials(); + + browser.runtime.sendMessage({ + action: 'page_clear_logins' + }); + + // Switch back to default popup + browser.runtime.sendMessage({ + action: 'get_status' + }); + } else { + if (response.new !== 'no-hash' && response.new !== response.old) { + browser.runtime.sendMessage({ + action: 'get_settings', + }).then((response) => { + cip.settings = response.data; + cip.initCredentialFields(true); + }); + } + } + }); + } + }, 2000); +}; + cip.initCredentialFields = function(forceCall) { if (_called.initCredentialFields && !forceCall) { return; diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json index 11c52bd..c90921c 100644 --- a/keepassxc-browser/manifest.json +++ b/keepassxc-browser/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "keepassxc-browser", - "version": "0.3.2", + "version": "0.3.5", "description": "KeePassXC integration for modern web browsers", "author": "Sami Vänttinen", "icons": { @@ -41,7 +41,7 @@ "matches": [ "" ], - "js": [ + "js": [ "browser-polyfill.min.js", "global.js", "jquery-3.2.1.min.js", diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 15b6ad9..9da6052 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -40,7 +40,7 @@
- +
@@ -56,7 +56,7 @@
- +
@@ -74,7 +74,7 @@
- +
@@ -90,7 +90,7 @@
- +
Change the port if you have trouble with running KeePassXC on the default port. @@ -160,7 +160,7 @@
Latest available version of KeePassXC: - +

@@ -205,12 +205,12 @@ - +
- +
@@ -257,7 +257,7 @@ - + @@ -286,7 +286,7 @@

About


- Developed by Perry Nguyen, Lukas Schulze and + Developed by Perry Nguyen, Lukas Schulze and Sami Vänttinen

diff --git a/keepassxc-browser/popups/popup.css b/keepassxc-browser/popups/popup.css index 006881d..0dd0120 100644 --- a/keepassxc-browser/popups/popup.css +++ b/keepassxc-browser/popups/popup.css @@ -1,7 +1,8 @@ body { font-family: sans-serif; - min-width:357px; - overflow-x:hidden; + min-width: 440px; + max-width: 440px; + overflow-x: hidden; background-color: #eee; font-size: 15px; padding: 8px; @@ -17,19 +18,20 @@ body { list-style: none; padding-top: 1px; padding-bottom: 1px; - padding-left: 10px; + padding-left: 5px; padding-right: 5px; margin-left: 10px; margin-right: 5px; } .credentials a { - cursor: pointer; - display: block; - color: #336; + color: #333; +} +.credentials a:hover { + text-decoration: none; } .credentials li:hover { cursor: pointer; - background: #ccc; + background: #ddd; } .settings { @@ -37,6 +39,7 @@ body { margin-bottom: 10px; border-bottom: 1px solid #333; font-size: 90%; + text-align: left; } .settings label { display: block; @@ -56,20 +59,16 @@ body { color: #787878; } #update-available { - display: none; - font-size: 90%; - color: #cc0000; - font-weight: bold; - border-top: 1px solid #333; padding-top: 10px; margin-top: 10px; -} -#update-available a:link, -#update-available a:visited { - text-decoration: underline; - color: #cc0000; + padding-bottom: 0px; + margin-bottom: 0px; + text-align: left; } #update-available a:hover, #update-available a:active { - text-decoration: none; -} \ No newline at end of file + text-decoration: underline; +} +#lock-database-button { + float: right; +} diff --git a/keepassxc-browser/popups/popup.html b/keepassxc-browser/popups/popup.html index 1fef03b..76647c9 100644 --- a/keepassxc-browser/popups/popup.html +++ b/keepassxc-browser/popups/popup.html @@ -12,84 +12,87 @@ -

- - +
+
+ + + -
- You use an old version of KeePassXC. -
- Please download the latest version from keepassxc.org. +
+ You use an old version of KeePassXC. +
+ Please download the latest version from keepassxc.org. +
-
-
-

Checking status...

-
- -