diff --git a/CHANGELOG b/CHANGELOG index 979cd36..afe4ec6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,7 @@ - Fixed HTTP authentication with multiple credentials (credits to smorks) - Fixed error handling when decrypt fails - Fixed database-locked response handling -- TODO: Fixed nonce increment when encrypting messages +- Fixed nonce increment when encrypting messages 0.4.1 (18-11-2017) ========================= diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index ddda312..ae435ac 100644 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -134,8 +134,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password, page.tabs[tab.id].errorMessage = null; keepass.testAssociation((response) => { - if (!response) - { + if (!response) { browserAction.showDefault(null, tab); callback([]); return; @@ -144,6 +143,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password, const kpAction = kpActions.SET_LOGIN; const {dbid} = keepass.getCryptoKey(); const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); let messageData = { action: kpAction, @@ -164,7 +164,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password, nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -176,7 +176,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password, const message = nacl.util.encodeUTF8(res); const parsed = JSON.parse(message); - callback(keepass.verifyResponse(parsed, keepass.incrementedNonce(nonce)) ? 'success' : 'error'); + callback(keepass.verifyResponse(parsed, incrementedNonce) ? 'success' : 'error'); } else if (response.error && response.errorCode) { keepass.handleError(tab, response.errorCode, response.error); @@ -192,8 +192,7 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb page.debug('keepass.retrieveCredentials(callback, {1}, {2}, {3}, {4})', tab.id, url, submiturl, forceCallback); keepass.testAssociation((response) => { - if (!response) - { + if (!response) { browserAction.showDefault(null, tab); if (forceCallback) { callback([]); @@ -211,6 +210,7 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb let entries = []; const kpAction = kpActions.GET_LOGINS; const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); const {dbid} = keepass.getCryptoKey(); let messageData = { @@ -229,7 +229,7 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -243,7 +243,7 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb const parsed = JSON.parse(message); keepass.setcurrentKeePassXCVersion(parsed.version); - if (keepass.verifyResponse(parsed, keepass.incrementedNonce(nonce))) { + if (keepass.verifyResponse(parsed, incrementedNonce)) { entries = parsed.entries; keepass.updateLastUsed(keepass.databaseHash); if (entries.length === 0) { @@ -274,8 +274,7 @@ keepass.generatePassword = function(callback, tab, forceCallback) { } keepass.testAssociation((taresponse) => { - if (!taresponse) - { + if (!taresponse) { browserAction.showDefault(null, tab); if (forceCallback) { callback([]); @@ -291,13 +290,14 @@ keepass.generatePassword = function(callback, tab, forceCallback) { let passwords = []; const kpAction = kpActions.GENERATE_PASSWORD; const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); const request = { action: kpAction, nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -311,7 +311,7 @@ keepass.generatePassword = function(callback, tab, forceCallback) { const parsed = JSON.parse(message); keepass.setcurrentKeePassXCVersion(parsed.version); - if (keepass.verifyResponse(parsed, keepass.incrementedNonce(nonce))) { + if (keepass.verifyResponse(parsed, incrementedNonce)) { if (parsed.entries) { passwords = parsed.entries; keepass.updateLastUsed(keepass.databaseHash); @@ -349,6 +349,7 @@ keepass.associate = function(callback, tab) { const kpAction = kpActions.ASSOCIATE; const key = nacl.util.encodeBase64(keepass.keyPair.publicKey); const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); const messageData = { action: kpAction, @@ -361,7 +362,7 @@ keepass.associate = function(callback, tab) { nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -375,7 +376,7 @@ keepass.associate = function(callback, tab) { keepass.setcurrentKeePassXCVersion(parsed.version); const id = parsed.id; - if (!keepass.verifyResponse(parsed, keepass.incrementedNonce(nonce))) { + if (!keepass.verifyResponse(parsed, incrementedNonce)) { keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED); } else { @@ -424,6 +425,7 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) { const kpAction = kpActions.TEST_ASSOCIATE; const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); const {dbid, dbkey} = keepass.getCryptoKey(); if (dbkey === null || dbid === null) { @@ -446,7 +448,7 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) { nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request, enableTimeout).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -461,7 +463,7 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) { keepass.setcurrentKeePassXCVersion(parsed.version); keepass.isEncryptionKeyUnrecognized = false; - if (!keepass.verifyResponse(parsed, keepass.incrementedNonce(nonce))) { + if (!keepass.verifyResponse(parsed, incrementedNonce)) { const hash = response.hash || 0; keepass.deleteKey(hash); keepass.isEncryptionKeyUnrecognized = true; @@ -499,6 +501,7 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) { const kpAction = kpActions.GET_DATABASE_HASH; const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); const messageData = { action: kpAction @@ -517,7 +520,7 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) { nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request, enableTimeout).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -529,7 +532,6 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) { const message = nacl.util.encodeUTF8(res); const parsed = JSON.parse(message); - if (parsed.hash) { const oldDatabaseHash = keepass.databaseHash; keepass.setcurrentKeePassXCVersion(parsed.version); @@ -543,12 +545,14 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) { keepass.isDatabaseClosed = false; keepass.isKeePassXCAvailable = true; callback(parsed.hash); + return; } else if (parsed.errorCode) { keepass.databaseHash = 'no-hash'; keepass.isDatabaseClosed = true; keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED); callback(keepass.databaseHash); + return; } } else { @@ -562,6 +566,7 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) { keepass.handleError(tab, response.errorCode, response.error); } callback(keepass.databaseHash); + return; } }); }; @@ -576,6 +581,7 @@ keepass.changePublicKeys = function(tab, enableTimeout = false) { const kpAction = kpActions.CHANGE_PUBLIC_KEYS; const key = nacl.util.encodeBase64(keepass.keyPair.publicKey); const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize)); const request = { @@ -584,11 +590,11 @@ keepass.changePublicKeys = function(tab, enableTimeout = false) { nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request, enableTimeout).then((response) => { keepass.setcurrentKeePassXCVersion(response.version); - if (!keepass.verifyKeyResponse(response, key, keepass.incrementedNonce(nonce))) { + if (!keepass.verifyKeyResponse(response, key, incrementedNonce)) { if (tab && page.tabs[tab.id]) { keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED); reject(false); @@ -612,6 +618,7 @@ keepass.lockDatabase = function(tab) { const kpAction = kpActions.LOCK_DATABASE; const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); const messageData = { action: kpAction @@ -623,7 +630,7 @@ keepass.lockDatabase = function(tab) { nonce: nonce, clientID: keepass.clientID }; - console.log(kpAction + " " + nacl.util.decodeBase64(nonce)); + keepass.sendNativeMessage(request).then((response) => { if (response.message && response.nonce) { const res = keepass.decrypt(response.message, response.nonce); @@ -637,7 +644,7 @@ keepass.lockDatabase = function(tab) { const parsed = JSON.parse(message); keepass.setcurrentKeePassXCVersion(parsed.version); - if (keepass.verifyResponse(parsed, keepass.incrementedNonce(nonce))) { + if (keepass.verifyResponse(parsed, incrementedNonce)) { keepass.isDatabaseClosed = true; // Display error message in the popup @@ -826,46 +833,21 @@ function onDisconnected() { keepass.getNonce = function() { return nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize)); - - // New implementation - const oldNonce = nacl.util.decodeBase64(keepass.nonce); - - let newNonce = []; - for (let i = 0; i < 24; i++){ - newNonce[i] = oldNonce[i]; - } - - for (let i = 0; i < 24; i++) { - newNonce[i]++; - if (newNonce[i]) { - break; - } - } - - console.log("New: " + newNonce); - console.log("Old: " + oldNonce); - keepass.nonce = nacl.util.encodeBase64(newNonce); - return nacl.util.encodeBase64(oldNonce); }; keepass.incrementedNonce = function(nonce) { const oldNonce = nacl.util.decodeBase64(nonce); + let newNonce = oldNonce.slice(0); - // TODO: fix the incrementation, it's not complete yet - let newNonce = []; - for (let i = 0; i < 24; i++){ - newNonce[i] = oldNonce[i]; + // from libsodium/utils.c + let i = 0; + let c = 1; + for (; i < newNonce.length; ++i) { + c += newNonce[i]; + newNonce[i] = c; + c >>= 8; } - for (let i = 0; i < 24; i++) { - newNonce[i]++; - if (newNonce[i]) { - break; - } - } - - console.log("New: " + newNonce); - console.log("Old: " + oldNonce); return nacl.util.encodeBase64(newNonce); }; diff --git a/keepassxc-protocol.md b/keepassxc-protocol.md index 703d627..8ac2107 100644 --- a/keepassxc-protocol.md +++ b/keepassxc-protocol.md @@ -7,7 +7,7 @@ Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweet 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. +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. Encrypted messages are built with these JSON parameters: