From dc15e6130472a751f0b2c994d86201b816d12df3 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sun, 22 Oct 2017 17:52:55 +0300 Subject: [PATCH] Improved credentials check --- CHANGELOG | 4 + README.md | 240 +--------------------- keepassxc-browser/background/event.js | 4 +- keepassxc-browser/background/keepass.js | 4 + keepassxc-browser/background/page.js | 6 +- keepassxc-browser/keepassxc-browser.js | 13 +- keepassxc-browser/manifest.json | 2 +- keepassxc-protocol.md | 253 ++++++++++++++++++++++++ 8 files changed, 276 insertions(+), 250 deletions(-) create mode 100644 keepassxc-protocol.md diff --git a/CHANGELOG b/CHANGELOG index 58e4045..fa98f6f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.3.7 (22-10-2017) +========================= +- Improved credentials check (does not use protocol requests for polling) + 0.3.6 (20-10-2017) ========================= - Restricted page credentials polling to active tab diff --git a/README.md b/README.md index 892d330..a4d2392 100644 --- a/README.md +++ b/README.md @@ -37,245 +37,7 @@ The following improvements and features have been made after the fork. At this p ## Protocol -Transmitting messages between KeePassXC and keepassxc-browser is totally rewritten. This is still under development. -Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweetnacl-js) box method and does the following: - -1. keepassxc-browser generates a key pair (with public and secret key) and transfers the public key to KeePassXC -2. When KeePassXC receives the public key it generates its own key pair and transfers the public key to keepassxc-browser -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 etc. -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. - -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: -```javascript -{ - "action": "change-public-keys", - "publicKey": "", - "proxyPort": "", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "clientID": "" -} -``` - -Response (success): -```javascript -{ - "action": "change-public-keys", - "version": "2.2.0", - "publicKey": "", - "success": "true" -} -``` - -### get-databasehash -Request (unencrypted): -```javascript -{ - "action": "get-databasehash" -} -``` - -Response message data (success, decrypted): -```javascript -{ - "action": "hash", - "hash": "29234e32274a32276e25666a42", - "version": "2.2.0" -} -``` - -### associate -Unencrypted message: -```javascript -{ - "action": "associate", - "key": "" -} -``` - -Request: -```javascript -{ - "action": "associate", - "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "clientID": "" -} -``` - -Response message data (success, decrypted): -```javascript -{ - "hash": "29234e32274a32276e25666a42", - "version": "2.2.0", - "success": "true", - "id": "testclient", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" -} -``` - -### test-associate -Unencrypted message: -```javascript -{ - "action": "test-associate", - "id": "", - "key": "", - "clientID": "" -} -``` - -Request: -```javascript -{ - "action": "test-associate", - "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" -} -``` - -Response message data (success, decrypted): -```javascript -{ - "version": "2.2.0", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "hash": "29234e32274a32276e25666a42", - "id": "testclient", - "success": "true" -} -``` - -### generate-password -Request: -```javascript -{ - "action": "generate-password", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "clientID": "" -} -``` - -Response message data (success, decrypted): -```javascript -{ - "version": "2.2.0", - "entries": [ - { - "login": 144, - "password": "testclientpassword" - } - ], - "success": "true", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" -} -``` - -### get-logins -Unencrypted message: -```javascript -{ - "action": "get-logins", - "url": "", - "submitUrl": optional -} -``` - -Request: -```javascript -{ - "action": "get-logins", - "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "clientID": "" -} -``` - -Response message data (success, decrypted): -```javascript -{ - "count": "2", - "entries" : [ - { - "login": "user1", - "name": "user1", - "password": "passwd1" - }, - { - "login": "user2", - "name": "user2", - "password": "passwd2" - }], - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "success": "true", - "hash": "29234e32274a32276e25666a42", - "version": "2.2.0" -} -``` - -### set-login -Unencrypted message: -```javascript -{ - "action": "set-login", - "url": "", - "submitUrl": "", - "id": "testclient", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "login": "user1", - "password": "passwd1" -} -``` - -Request: -```javascript -{ - "action": "set-login", - "message": encryptedMessage - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "clientID": "" -} -``` - -Response message data (success, decrypted): -```javascript -{ - "count": null, - "entries" : null, - "error": "", - "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", - "success": "true", - "hash": "29234e32274a32276e25666a42", - "version": "2.2.0" -} -``` - -### 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" -} -``` +The details about the messaging protocol used with the browser extension and KeePassXC can be found [here](https://github.com/varjolintu/keepassxc-browser/blob/master/keepassxc-protocol.md). ## Licenses diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index a0de69b..2b0a440 100644 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -72,7 +72,7 @@ kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) { else { console.log('undefined handler for tab ' + tab.id); } - }); + }).catch((e) => {console.log(e);}); }; kpxcEvent.onShowAlert = function(callback, tab, message) { @@ -240,7 +240,7 @@ kpxcEvent.pageClearLogins = function(callback, tab) { kpxcEvent.oldDatabaseHash = 'no-hash'; kpxcEvent.checkDatabaseHash = function(callback, tab) { - keepass.getDatabaseHash((response) => { + keepass.checkDatabaseHash((response) => { callback({old: kpxcEvent.oldDatabaseHash, new: response}); kpxcEvent.oldDatabaseHash = response; }); diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index 69fb55b..39be7cb 100644 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -632,6 +632,10 @@ keepass.isConfigured = function(callback) { } }; +keepass.checkDatabaseHash = function(callback, tab) { + callback(keepass.databaseHash); +}; + keepass.isAssociated = function() { return (keepass.associated.value && keepass.associated.hash && keepass.associated.hash === keepass.databaseHash); }; diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 8ffd51e..d6ee9fc 100644 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -66,7 +66,7 @@ page.isValidProtocol = function(url) { page.switchTab = function(callback, tab) { browserAction.showDefault(null, tab); - browser.tabs.sendMessage(tab.id, {action: 'activated_tab'}).catch((e) => {console.log(e);}); + browser.tabs.sendMessage(tab.id, {action: 'activated_tab'}).catch((e) => {}); }; page.clearCredentials = function(tabId, complete) { @@ -82,7 +82,7 @@ page.clearCredentials = function(tabId, complete) { browser.tabs.sendMessage(tabId, { action: 'clear_credentials' - }).catch((e) => {console.log(e);}); + }).catch((e) => {}); } }; @@ -101,7 +101,7 @@ page.createTabEntry = function(tabId) { page.removePageInformationFromNotExistingTabs = function() { let rand = Math.floor(Math.random()*1001); if (rand === 28) { - browser.tabs.query({}, (tabs) => { + browser.tabs.query({}).then(function(tabs) { let $tabIds = {}; const $infoIds = Object.keys(page.tabs); diff --git a/keepassxc-browser/keepassxc-browser.js b/keepassxc-browser/keepassxc-browser.js index 34e2b5c..af6ecbc 100644 --- a/keepassxc-browser/keepassxc-browser.js +++ b/keepassxc-browser/keepassxc-browser.js @@ -46,9 +46,11 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) { } else if (req.action === 'clear_credentials') { cipEvents.clearCredentials(); + callback(); } else if (req.action === 'activated_tab') { cipEvents.triggerActivatedTab(); + callback(); } else if (req.action === 'redetect_fields') { browser.runtime.sendMessage({ @@ -254,7 +256,7 @@ cipPassword.createDialog = function() { e.preventDefault(); browser.runtime.sendMessage({ action: 'generate_password' - }).then(cipPassword.callbackGeneratedPassword); + }).then(cipPassword.callbackGeneratedPassword).catch((e) => {console.log(e);}); } }, 'Copy': @@ -1129,6 +1131,7 @@ cip.detectNewActiveFields = function() { //} }; +// Try to do this in a way that database value if checked without polling the KeePassXC.. too many messages jumping around // Switch credentials if database is changed or closed cip.detectDatabaseChange = function() { const dbDetectInterval = setInterval(function() { @@ -1157,9 +1160,9 @@ cip.detectDatabaseChange = function() { }); } } - }); + }).catch((e) => {console.log(e);}); } - }, 2000); + }, 1000); }; cip.initCredentialFields = function(forceCall) { @@ -1193,7 +1196,7 @@ cip.initCredentialFields = function(forceCall) { browser.runtime.sendMessage({ action: 'retrieve_credentials', args: [ cip.url, cip.submitUrl ] - }).then(cip.retrieveCredentialsCallback); + }).then(cip.retrieveCredentialsCallback).catch((e) => {console.log(e);}); } }); }; @@ -1747,6 +1750,6 @@ cipEvents.triggerActivatedTab = function() { browser.runtime.sendMessage({ action: 'retrieve_credentials', args: [ cip.url, cip.submitUrl ] - }).then(cip.retrieveCredentialsCallback); + }).then(cip.retrieveCredentialsCallback).catch((e) => {console.log(e);}); } }; diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json index e8b4412..9d8273b 100644 --- a/keepassxc-browser/manifest.json +++ b/keepassxc-browser/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "keepassxc-browser", - "version": "0.3.6", + "version": "0.3.7", "description": "KeePassXC integration for modern web browsers", "author": "Sami Vänttinen", "icons": { diff --git a/keepassxc-protocol.md b/keepassxc-protocol.md new file mode 100644 index 0000000..872bd08 --- /dev/null +++ b/keepassxc-protocol.md @@ -0,0 +1,253 @@ +## keepassxc-protocol + +Transmitting messages between KeePassXC and keepassxc-browser is totally rewritten. This is still under development. +Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweetnacl-js) box method and does the following: + +1. keepassxc-browser generates a key pair (with public and secret key) and transfers the public key to KeePassXC +2. When KeePassXC receives the public key it generates its own key pair and transfers the public key to keepassxc-browser +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 etc. +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. + +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. + +Currently these messages are implemented: +- `change-public-keys`: Request for passing public keys from client to server and back. +- `get-databasehash`: Request for receiving the database hash (SHA256) of the current active database. +- `associate`: Request for associating a new client with KeePassXC. +- `test-associate`: Request for testing if the client has been associated with KeePassXC. +- `generate-password`: Request for generating a password. KeePassXC's settings are used. +- `get-logins`: Requests for receiving credentials for the current URL match. +- `set-login`: Request for adding or updating credentials to the database. +- `lock-database`: Request for locking the database from client. +- `database-locked`: A signal from KeePassXC, the current active database is locked. +- `database-unlocked`: A signal from KeePassXC, the current active database is unlocked. + +### change-public-keys +Request: +```javascript +{ + "action": "change-public-keys", + "publicKey": "", + "proxyPort": "", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" +} +``` + +Response (success): +```javascript +{ + "action": "change-public-keys", + "version": "2.2.0", + "publicKey": "", + "success": "true" +} +``` + +### get-databasehash +Request (unencrypted): +```javascript +{ + "action": "get-databasehash" +} +``` + +Response message data (success, decrypted): +```javascript +{ + "action": "hash", + "hash": "29234e32274a32276e25666a42", + "version": "2.2.0" +} +``` + +### associate +Unencrypted message: +```javascript +{ + "action": "associate", + "key": "" +} +``` + +Request: +```javascript +{ + "action": "associate", + "message": encryptedMessage + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" +} +``` + +Response message data (success, decrypted): +```javascript +{ + "hash": "29234e32274a32276e25666a42", + "version": "2.2.0", + "success": "true", + "id": "testclient", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" +} +``` + +### test-associate +Unencrypted message: +```javascript +{ + "action": "test-associate", + "id": "", + "key": "", + "clientID": "" +} +``` + +Request: +```javascript +{ + "action": "test-associate", + "message": encryptedMessage + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" +} +``` + +Response message data (success, decrypted): +```javascript +{ + "version": "2.2.0", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "hash": "29234e32274a32276e25666a42", + "id": "testclient", + "success": "true" +} +``` + +### generate-password +Request: +```javascript +{ + "action": "generate-password", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" +} +``` + +Response message data (success, decrypted): +```javascript +{ + "version": "2.2.0", + "entries": [ + { + "login": 144, + "password": "testclientpassword" + } + ], + "success": "true", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" +} +``` + +### get-logins +Unencrypted message: +```javascript +{ + "action": "get-logins", + "url": "", + "submitUrl": optional +} +``` + +Request: +```javascript +{ + "action": "get-logins", + "message": encryptedMessage + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" +} +``` + +Response message data (success, decrypted): +```javascript +{ + "count": "2", + "entries" : [ + { + "login": "user1", + "name": "user1", + "password": "passwd1" + }, + { + "login": "user2", + "name": "user2", + "password": "passwd2" + }], + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "success": "true", + "hash": "29234e32274a32276e25666a42", + "version": "2.2.0" +} +``` + +### set-login +Unencrypted message: +```javascript +{ + "action": "set-login", + "url": "", + "submitUrl": "", + "id": "testclient", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "login": "user1", + "password": "passwd1" +} +``` + +Request: +```javascript +{ + "action": "set-login", + "message": encryptedMessage + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "clientID": "" +} +``` + +Response message data (success, decrypted): +```javascript +{ + "count": null, + "entries" : null, + "error": "", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q", + "success": "true", + "hash": "29234e32274a32276e25666a42", + "version": "2.2.0" +} +``` + +### 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" +} +``` \ No newline at end of file