diff --git a/.eslintrc b/.eslintrc index 48b8ec9..034b127 100644 --- a/.eslintrc +++ b/.eslintrc @@ -83,6 +83,7 @@ "browserAction": true, "createStylesheet": true, "DatabaseState": true, + "EXTENSION_NAME": true, "httpAuth": true, "Icon": true, "isEdge": true, @@ -94,7 +95,9 @@ "initColorTheme": true, "jQuery": true, "keepass": true, + "keepassClient": true, "kpxc": true, + "kpActions": true, "kpxcBanner": true, "kpxcDefine": true, "kpxcFields": true, diff --git a/keepassxc-browser/background/client.js b/keepassxc-browser/background/client.js new file mode 100644 index 0000000..f78afa8 --- /dev/null +++ b/keepassxc-browser/background/client.js @@ -0,0 +1,335 @@ +'use strict'; + +const keepassClient = {}; +keepassClient.keySize = 24; +keepassClient.messageTimeout = 500; // Milliseconds +keepassClient.nativeHostName = 'org.keepassxc.keepassxc_browser'; +keepassClient.nativePort = null; + +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, + INCORRECT_ACTION: 12, + EMPTY_MESSAGE_RECEIVED: 13, + NO_URL_PROVIDED: 14, + NO_LOGINS_FOUND: 15, + + errorMessages: { + 0: { msg: tr('errorMessageUnknown') }, + 1: { msg: tr('errorMessageDatabaseNotOpened') }, + 2: { msg: tr('errorMessageDatabaseHash') }, + 3: { msg: tr('errorMessageClientPublicKey') }, + 4: { msg: tr('errorMessageDecrypt') }, + 5: { msg: tr('errorMessageTimeout') }, + 6: { msg: tr('errorMessageCanceled') }, + 7: { msg: tr('errorMessageEncrypt') }, + 8: { msg: tr('errorMessageAssociate') }, + 9: { msg: tr('errorMessageKeyExchange') }, + 10: { msg: tr('errorMessageEncryptionKey') }, + 11: { msg: tr('errorMessageSavedDatabases') }, + 12: { msg: tr('errorMessageIncorrectAction') }, + 13: { msg: tr('errorMessageEmptyMessage') }, + 14: { msg: tr('errorMessageNoURL') }, + 15: { msg: tr('errorMessageNoLogins') } + }, + + getError(errorCode) { + return this.errorMessages[errorCode].msg; + } +}; + +const messageBuffer = { + buffer: [], + + addMessage(msg) { + if (!this.buffer.includes(msg)) { + this.buffer.push(msg); + } + }, + + matchAndRemove(msg) { + for (let i = 0; i < this.buffer.length; ++i) { + if (msg.nonce && msg.nonce === keepassClient.incrementedNonce(this.buffer[i].nonce)) { + this.buffer.splice(i, 1); + return true; + } + } + + return false; + } +}; + +//-------------------------------------------------------------------------- +// Messaging +//-------------------------------------------------------------------------- + +keepassClient.sendNativeMessage = function(request, enableTimeout = false, timeoutValue) { + return new Promise((resolve, reject) => { + let timeout; + const requestAction = request.action; + const ev = keepassClient.nativePort.onMessage; + + const listener = ((port, action) => { + const handler = (msg) => { + if (msg && msg.action === action) { + // Only resolve a matching response or a notification (without nonce) + if (!msg.nonce || messageBuffer.matchAndRemove(msg)) { + port.removeListener(handler); + if (enableTimeout) { + clearTimeout(timeout); + } + + resolve(msg); + return; + } + } + }; + return handler; + })(ev, requestAction); + ev.addListener(listener); + + const messageTimeout = timeoutValue || keepassClient.messageTimeout; + + // Handle timeouts + if (enableTimeout) { + timeout = setTimeout(() => { + const errorMessage = { + action: requestAction, + error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED), + errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED + }; + keepass.isKeePassXCAvailable = false; + ev.removeListener(listener.handler); + resolve(errorMessage); + }, messageTimeout); + } + + // Store the request to the buffer + messageBuffer.addMessage(request); + + // Send the request + if (keepassClient.nativePort) { + keepassClient.nativePort.postMessage(request); + } + }); +}; + +keepassClient.handleResponse = function(response, incrementedNonce, tab) { + if (response.message && response.nonce) { + const res = keepassClient.decrypt(response.message, response.nonce); + if (!res) { + keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); + return undefined; + } + + const message = nacl.util.encodeUTF8(res); + const parsed = JSON.parse(message); + + if (keepassClient.verifyResponse(parsed, incrementedNonce)) { + return parsed; + } + } else if (response.error && response.errorCode) { + keepass.handleError(tab, response.errorCode, response.error); + } + + return undefined; +}; + +keepassClient.buildRequest = function(action, encrypted, nonce, clientID, triggerUnlock = false) { + const request = { + action: action, + message: encrypted, + nonce: nonce, + clientID: clientID + }; + + if (triggerUnlock) { + request.triggerUnlock = 'true'; + } + + return request; +}; + +keepassClient.sendMessage = async function(kpAction, tab, messageData, nonce, enableTimeout = false, triggerUnlock = false) { + const request = keepassClient.buildRequest(kpAction, keepassClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock); + const response = await keepassClient.sendNativeMessage(request, enableTimeout); + + const incrementedNonce = keepassClient.incrementedNonce(nonce); + return keepassClient.handleResponse(response, incrementedNonce, tab); +}; + +//-------------------------------------------------------------------------- +// Utils +//-------------------------------------------------------------------------- + +keepassClient.getNonce = function() { + return nacl.util.encodeBase64(nacl.randomBytes(keepassClient.keySize)); +}; + +keepassClient.incrementedNonce = function(nonce) { + const oldNonce = nacl.util.decodeBase64(nonce); + const newNonce = oldNonce.slice(0); + + // from libsodium/utils.c + let i = 0; + let c = 1; + for (; i < newNonce.length; ++i) { + c += newNonce[i]; + newNonce[i] = c; + c >>= 8; + } + + return nacl.util.encodeBase64(newNonce); +}; + +keepassClient.getNonces = function() { + const nonce = keepassClient.getNonce(); + const incrementedNonce = keepassClient.incrementedNonce(nonce); + return [ nonce, incrementedNonce ]; +}; + +keepassClient.verifyKeyResponse = function(response, key, nonce) { + if (!response.success || !response.publicKey) { + keepass.associated.hash = null; + return false; + } + + if (!keepassClient.checkNonceLength(response.nonce)) { + console.log(`${EXTENSION_NAME}: Error. Invalid nonce length`); + return false; + } + + const reply = (response.nonce === nonce); + if (response.publicKey && reply) { + keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey); + return true; + } + + return reply; +}; + +keepassClient.verifyResponse = function(response, nonce, id) { + keepass.associated.value = response.success; + if (response.success !== 'true') { + keepass.associated.hash = null; + return false; + } + + keepass.associated.hash = keepass.databaseHash; + + if (!keepassClient.checkNonceLength(response.nonce)) { + return false; + } + + keepass.associated.value = (response.nonce === nonce); + if (keepass.associated.value === false) { + console.log(`${EXTENSION_NAME}: Error. Nonce compare failed`); + return false; + } + + if (id) { + keepass.associated.value = (keepass.associated.value && id === response.id); + } + + keepass.associated.hash = (keepass.associated.value) ? keepass.databaseHash : null; + return keepass.isAssociated(); +}; + +keepassClient.verifyDatabaseResponse = function(response, nonce) { + if (response.success !== 'true') { + keepass.associated.hash = null; + return false; + } + + if (!keepassClient.checkNonceLength(response.nonce)) { + console.log(`${EXTENSION_NAME}: Error. Invalid nonce length`); + return false; + } + + if (response.nonce !== nonce) { + console.log(`${EXTENSION_NAME}: Error- Nonce compare failed`); + return false; + } + + keepass.associated.hash = response.hash; + return response.hash !== '' && response.success === 'true'; +}; + +keepassClient.checkNonceLength = function(nonce) { + return nacl.util.decodeBase64(nonce).length === nacl.secretbox.nonceLength; +}; + +keepassClient.encrypt = function(input, nonce) { + const messageData = nacl.util.decodeUTF8(JSON.stringify(input)); + const messageNonce = nacl.util.decodeBase64(nonce); + + if (keepass.serverPublicKey) { + const message = nacl.box(messageData, messageNonce, keepass.serverPublicKey, keepass.keyPair.secretKey); + if (message) { + return nacl.util.encodeBase64(message); + } + } + + return ''; +}; + +keepassClient.decrypt = function(input, nonce) { + const m = nacl.util.decodeBase64(input); + const n = nacl.util.decodeBase64(nonce); + const res = nacl.box.open(m, n, keepass.serverPublicKey, keepass.keyPair.secretKey); + return res; +}; + +//-------------------------------------------------------------------------- +// Native Messaging related +//-------------------------------------------------------------------------- + +keepassClient.connectToNative = function() { + if (keepassClient.nativePort) { + keepassClient.nativePort.disconnect(); + } + keepassClient.nativeConnect(); +}; + +keepassClient.nativeConnect = function() { + console.log(`${EXTENSION_NAME}: Connecting to native messaging host ${keepassClient.nativeHostName}`); + keepassClient.nativePort = browser.runtime.connectNative(keepassClient.nativeHostName); + keepassClient.nativePort.onMessage.addListener(keepassClient.onNativeMessage); + keepassClient.nativePort.onDisconnect.addListener(onDisconnected); + keepass.isConnected = true; + return keepassClient.nativePort; +}; + +function onDisconnected() { + keepassClient.nativePort = null; + keepass.isConnected = false; + keepass.isDatabaseClosed = true; + keepass.isKeePassXCAvailable = false; + keepass.associated.value = false; + keepass.associated.hash = null; + keepass.databaseHash = ''; + + page.clearCredentials(page.currentTabId, true); + keepass.updatePopup('cross'); + keepass.updateDatabaseHashToContent(); + console.log(`${EXTENSION_NAME}: Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)}`); +} + +keepassClient.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.updateDatabase(); + } +}; diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index 8fc27f7..22e7764 100755 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -198,6 +198,10 @@ kpxcEvent.compareVersion = async function(tab, args = []) { return keepass.compareVersion(args[0], args[1]); }; +kpxcEvent.getIsKeePassXCAvailable = async function() { + return keepass.isKeePassXCAvailable; +}; + // All methods named in this object have to be declared BEFORE this! kpxcEvent.messageHandlers = { 'add_credentials': keepass.addCredentials, @@ -218,7 +222,7 @@ kpxcEvent.messageHandlers = { 'get_tab_information': kpxcEvent.onGetTabInformation, 'get_totp': keepass.getTotp, 'init_http_auth': kpxcEvent.initHttpAuth, - 'is_connected': keepass.getIsKeePassXCAvailable, + 'is_connected': kpxcEvent.getIsKeePassXCAvailable, 'load_keyring': kpxcEvent.onLoadKeyRing, 'load_settings': kpxcEvent.onLoadSettings, 'lock_database': kpxcEvent.lockDatabase, diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index e8b0d73..53c7d0d 100755 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -10,18 +10,13 @@ keepass.isDatabaseClosed = true; keepass.isKeePassXCAvailable = false; keepass.isEncryptionKeyUnrecognized = false; keepass.currentKeePassXC = ''; -keepass.requiredKeePassXC = '2.3.0'; -keepass.nativeHostName = 'org.keepassxc.keepassxc_browser'; -keepass.nativePort = null; -keepass.keySize = 24; +keepass.requiredKeePassXC = '2.3.1'; keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest'; keepass.cacheTimeout = 30 * 1000; // Milliseconds keepass.databaseHash = ''; keepass.previousDatabaseHash = ''; keepass.keyId = 'keepassxc-browser-cryptokey-name'; keepass.keyBody = 'keepassxc-browser-key'; -keepass.messageTimeout = 500; // Milliseconds -keepass.nonce = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize)); keepass.reconnectLoop = null; const kpActions = { @@ -41,124 +36,14 @@ const kpActions = { REQUEST_AUTOTYPE: 'request-autotype' }; -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, - INCORRECT_ACTION: 12, - EMPTY_MESSAGE_RECEIVED: 13, - NO_URL_PROVIDED: 14, - NO_LOGINS_FOUND: 15, - - errorMessages: { - 0: { msg: tr('errorMessageUnknown') }, - 1: { msg: tr('errorMessageDatabaseNotOpened') }, - 2: { msg: tr('errorMessageDatabaseHash') }, - 3: { msg: tr('errorMessageClientPublicKey') }, - 4: { msg: tr('errorMessageDecrypt') }, - 5: { msg: tr('errorMessageTimeout') }, - 6: { msg: tr('errorMessageCanceled') }, - 7: { msg: tr('errorMessageEncrypt') }, - 8: { msg: tr('errorMessageAssociate') }, - 9: { msg: tr('errorMessageKeyExchange') }, - 10: { msg: tr('errorMessageEncryptionKey') }, - 11: { msg: tr('errorMessageSavedDatabases') }, - 12: { msg: tr('errorMessageIncorrectAction') }, - 13: { msg: tr('errorMessageEmptyMessage') }, - 14: { msg: tr('errorMessageNoURL') }, - 15: { msg: tr('errorMessageNoLogins') } - }, - - getError(errorCode) { - return this.errorMessages[errorCode].msg; - } -}; - browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': null }, 'keyRing': {} }).then((item) => { keepass.latestKeePassXC = item.latestKeePassXC; keepass.keyRing = item.keyRing; }); -const messageBuffer = { - buffer: [], - - addMessage(msg) { - if (!this.buffer.includes(msg)) { - this.buffer.push(msg); - } - }, - - matchAndRemove(msg) { - for (let i = 0; i < this.buffer.length; ++i) { - if (msg.nonce && msg.nonce === keepass.incrementedNonce(this.buffer[i].nonce)) { - this.buffer.splice(i, 1); - return true; - } - } - - return false; - } -}; - -keepass.sendNativeMessage = function(request, enableTimeout = false, timeoutValue) { - return new Promise((resolve, reject) => { - let timeout; - const requestAction = request.action; - const ev = keepass.nativePort.onMessage; - - const listener = ((port, action) => { - const handler = (msg) => { - if (msg && msg.action === action) { - // Only resolve a matching response or a notification (without nonce) - if (!msg.nonce || messageBuffer.matchAndRemove(msg)) { - port.removeListener(handler); - if (enableTimeout) { - clearTimeout(timeout); - } - - resolve(msg); - return; - } - } - }; - return handler; - })(ev, requestAction); - ev.addListener(listener); - - const messageTimeout = timeoutValue || keepass.messageTimeout; - - // Handle timeouts - if (enableTimeout) { - timeout = setTimeout(() => { - const errorMessage = { - action: requestAction, - error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED), - errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED - }; - keepass.isKeePassXCAvailable = false; - ev.removeListener(listener.handler); - resolve(errorMessage); - }, messageTimeout); - } - - // Store the request to the buffer - messageBuffer.addMessage(request); - - // Send the request - if (keepass.nativePort) { - keepass.nativePort.postMessage(request); - } - }); -}; +//-------------------------------------------------------------------------- +// Commands +//-------------------------------------------------------------------------- keepass.addCredentials = async function(tab, args = []) { const [ username, password, url, group, groupUuid ] = args; @@ -176,7 +61,7 @@ keepass.updateCredentials = async function(tab, args = []) { const kpAction = kpActions.SET_LOGIN; const [ dbid ] = keepass.getCryptoKey(); - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const messageData = { action: kpAction, @@ -200,33 +85,20 @@ keepass.updateCredentials = async function(tab, args = []) { messageData.groupUuid = groupUuid; } - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return 'error'; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { // KeePassXC versions lower than 2.5.0 will have an empty parsed.error let successMessage = parsed.error; - if (parsed.error === 'success' || parsed.error === '') { + if (response.error === 'success' || response.error === '') { successMessage = entryId ? 'updated' : 'created'; } - return keepass.verifyResponse(parsed, incrementedNonce) ? successMessage : 'error'; - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); + + return successMessage; + } else { return 'error'; } - - browserAction.showDefault(tab); - return []; } catch (err) { - console.log('updateCredentials failed: ', err); + console.log(`${EXTENSION_NAME}: updateCredentials failed: ${err}`); return []; } }; @@ -251,7 +123,7 @@ keepass.retrieveCredentials = async function(tab, args = []) { let entries = []; const keys = []; const kpAction = kpActions.GET_LOGINS; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const [ dbid ] = keepass.getCryptoKey(); for (const keyHash in keepass.keyRing) { @@ -276,40 +148,23 @@ keepass.retrieveCredentials = async function(tab, args = []) { messageData.httpAuth = 'true'; } - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return []; + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { + entries = removeDuplicateEntries(response.entries); + keepass.updateLastUsed(keepass.databaseHash); + + if (entries.length === 0) { + // Questionmark-icon is not triggered, so we have to trigger for the normal symbol + browserAction.showDefault(tab); } - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - keepass.setcurrentKeePassXCVersion(parsed.version); - - if (keepass.verifyResponse(parsed, incrementedNonce)) { - entries = removeDuplicateEntries(parsed.entries); - keepass.updateLastUsed(keepass.databaseHash); - if (entries.length === 0) { - // Questionmark-icon is not triggered, so we have to trigger for the normal symbol - browserAction.showDefault(tab); - } - - return entries; - } else { - console.log('RetrieveCredentials for ' + url + ' rejected'); - } - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); - return []; + return entries; } browserAction.showDefault(tab); return []; } catch (err) { - console.log('retrieveCredentials failed: ', err); + console.log(`${EXTENSION_NAME}: retrieveCredentials failed: ${err}`); return []; } }; @@ -332,41 +187,25 @@ keepass.generatePassword = async function(tab) { let password; const kpAction = kpActions.GENERATE_PASSWORD; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); - const request = { + const messageData = { action: kpAction, nonce: nonce, clientID: keepass.clientID }; - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return []; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - keepass.setcurrentKeePassXCVersion(parsed.version); - - if (keepass.verifyResponse(parsed, incrementedNonce)) { - password = parsed.entries ?? parsed.password; - keepass.updateLastUsed(keepass.databaseHash); - } else { - console.log('GeneratePassword rejected'); - } - - return password; - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { + password = response.entries ?? response.password; + keepass.updateLastUsed(keepass.databaseHash); + } else { + console.log(`${EXTENSION_NAME}: generatePassword rejected`); } return password; } catch (err) { - console.log('generatePassword failed: ', err); + console.log(`${EXTENSION_NAME}: generatePassword failed: ${err}`); return []; } }; @@ -388,7 +227,7 @@ keepass.associate = async function(tab) { const kpAction = kpActions.ASSOCIATE; const key = nacl.util.encodeBase64(keepass.keyPair.publicKey); - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const idKeyPair = nacl.box.keyPair(); const idKey = nacl.util.encodeBase64(idKeyPair.publicKey); @@ -398,39 +237,22 @@ keepass.associate = async function(tab) { idKey: idKey }; - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID, true); - - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return AssociatedAction.NOT_ASSOCIATED; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - keepass.setcurrentKeePassXCVersion(parsed.version); - const id = parsed.id; - - if (!keepass.verifyResponse(parsed, incrementedNonce)) { - keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED); - return AssociatedAction.NOT_ASSOCIATED; - } else { - // Use public key as identification key with older KeePassXC releases - const savedKey = keepass.compareVersion('2.3.4', keepass.currentKeePassXC) ? idKey : key; - keepass.setCryptoKey(id, savedKey); // Save the new identification public key as id key for the database - keepass.associated.value = true; - keepass.associated.hash = parsed.hash || 0; - } + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce, false, true); + if (response) { + // Use public key as identification key with older KeePassXC releases + const savedKey = keepass.compareVersion('2.3.4', keepass.currentKeePassXC) ? idKey : key; + keepass.setCryptoKey(response.id, savedKey); // Save the new identification public key as id key for the database + keepass.associated.value = true; + keepass.associated.hash = response.hash || 0; browserAction.show(tab); return AssociatedAction.NEW_ASSOCIATION; - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); } + + keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED); + return AssociatedAction.NOT_ASSOCIATED; } catch (err) { - console.log('associate failed: ', err); + console.log(`${EXTENSION_NAME}: associate failed: ${err}`); } return AssociatedAction.NOT_ASSOCIATED; @@ -464,7 +286,7 @@ keepass.testAssociation = async function(tab, args = []) { } const kpAction = kpActions.TEST_ASSOCIATE; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const [ dbid, dbkey ] = keepass.getCryptoKey(); if (dbkey === null || dbid === null) { @@ -480,43 +302,26 @@ keepass.testAssociation = async function(tab, args = []) { key: dbkey }; - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - - const response = await keepass.sendNativeMessage(request, enableTimeout); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return false; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - keepass.setcurrentKeePassXCVersion(parsed.version); + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce, enableTimeout); + if (!response) { + const hash = response.hash || 0; + keepass.deleteKey(hash); + keepass.isEncryptionKeyUnrecognized = true; + keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED); + keepass.associated.value = false; + keepass.associated.hash = null; + } else if (!keepass.isAssociated()) { + keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED); + } else { keepass.isEncryptionKeyUnrecognized = false; - - if (!keepass.verifyResponse(parsed, incrementedNonce)) { - const hash = response.hash || 0; - keepass.deleteKey(hash); - keepass.isEncryptionKeyUnrecognized = true; - keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED); - keepass.associated.value = false; - keepass.associated.hash = null; - } else if (!keepass.isAssociated()) { - keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED); - } else { - keepass.isEncryptionKeyUnrecognized = false; - if (tab && page.tabs[tab.id]) { - delete page.tabs[tab.id].errorMessage; - } + if (tab && page.tabs[tab.id]) { + delete page.tabs[tab.id].errorMessage; } - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); } return keepass.isAssociated(); } catch (err) { - console.log('testAssociation failed: ', err); + console.log(`${EXTENSION_NAME}: testAssociation failed: ${err}`); return false; } }; @@ -533,26 +338,25 @@ keepass.getDatabaseHash = async function(tab, args = []) { const [ enableTimeout = false, triggerUnlock = false ] = args; const kpAction = kpActions.GET_DATABASE_HASH; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const [ nonce, incrementedNonce ] = keepassClient.getNonces(); const messageData = { action: kpAction, connectedKeys: Object.keys(keepass.keyRing) // This will be removed in the future }; - const encrypted = keepass.encrypt(messageData, nonce); + const encrypted = keepassClient.encrypt(messageData, nonce); if (encrypted.length <= 0) { keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND); keepass.updateDatabaseHashToContent(); return keepass.databaseHash; } - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock); - try { - const response = await keepass.sendNativeMessage(request, enableTimeout); + const request = keepassClient.buildRequest(kpAction, keepassClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock); + const response = await keepassClient.sendNativeMessage(request, enableTimeout); if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); + const res = keepassClient.decrypt(response.message, response.nonce); if (!res) { keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); return ''; @@ -560,7 +364,7 @@ keepass.getDatabaseHash = async function(tab, args = []) { const message = nacl.util.encodeUTF8(res); const parsed = JSON.parse(message); - if (keepass.verifyDatabaseResponse(parsed, incrementedNonce) && parsed.hash) { + if (keepassClient.verifyDatabaseResponse(parsed, incrementedNonce) && parsed.hash) { const oldDatabaseHash = keepass.databaseHash; keepass.setcurrentKeePassXCVersion(parsed.version); keepass.databaseHash = parsed.hash || ''; @@ -599,7 +403,7 @@ keepass.getDatabaseHash = async function(tab, args = []) { } return keepass.databaseHash; } catch (err) { - console.log('getDatabaseHash failed: ', err); + console.log(`${EXTENSION_NAME}: getDatabaseHash failed: ${err}`); return keepass.databaseHash; } }; @@ -612,8 +416,8 @@ keepass.changePublicKeys = async function(tab, enableTimeout = false, connection const kpAction = kpActions.CHANGE_PUBLIC_KEYS; const key = nacl.util.encodeBase64(keepass.keyPair.publicKey); - const [ nonce, incrementedNonce ] = keepass.getNonces(); - keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize)); + const [ nonce, incrementedNonce ] = keepassClient.getNonces(); + keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepassClient.keySize)); const request = { action: kpAction, @@ -623,10 +427,10 @@ keepass.changePublicKeys = async function(tab, enableTimeout = false, connection }; try { - const response = await keepass.sendNativeMessage(request, enableTimeout, connectionTimeout); + const response = await keepassClient.sendNativeMessage(request, enableTimeout, connectionTimeout); keepass.setcurrentKeePassXCVersion(response.version); - if (!keepass.verifyKeyResponse(response, key, incrementedNonce)) { + if (!keepassClient.verifyKeyResponse(response, key, incrementedNonce)) { if (tab && page.tabs[tab.id]) { keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED); } @@ -636,10 +440,10 @@ keepass.changePublicKeys = async function(tab, enableTimeout = false, connection } keepass.isKeePassXCAvailable = true; - console.log('Server public key: ' + nacl.util.encodeBase64(keepass.serverPublicKey)); + console.log(`${EXTENSION_NAME}: Server public key: ${nacl.util.encodeBase64(keepass.serverPublicKey)}`); return true; } catch (err) { - console.log('changePublicKeys failed: ', err); + console.log(`${EXTENSION_NAME}: changePublicKeys failed: ${err}`); return false; } }; @@ -651,43 +455,29 @@ keepass.lockDatabase = async function(tab) { } const kpAction = kpActions.LOCK_DATABASE; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const messageData = { action: kpAction }; - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); try { - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return false; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - keepass.setcurrentKeePassXCVersion(parsed.version); - - if (keepass.verifyResponse(parsed, incrementedNonce)) { - keepass.isDatabaseClosed = true; - keepass.updateDatabase(); - - // Display error message in the popup - keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED); - return true; - } - } else if (response.error && response.errorCode) { + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { + keepass.isDatabaseClosed = true; + keepass.updateDatabase(); + + // Display error message in the popup + keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED); + return true; + } else { keepass.isDatabaseClosed = true; - keepass.handleError(tab, response.errorCode, response.error); } return false; } catch (err) { - console.log('lockDatabase failed: ', err); + console.log(`${EXTENSION_NAME}: lockDatabase failed: ${err}`); return false; } }; @@ -710,43 +500,25 @@ keepass.getDatabaseGroups = async function(tab) { let groups = []; const kpAction = kpActions.GET_DATABASE_GROUPS; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const messageData = { action: kpAction }; - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return []; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - - if (keepass.verifyResponse(parsed, incrementedNonce)) { - groups = parsed.groups; - groups.defaultGroup = page.settings.defaultGroup; - groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk; - keepass.updateLastUsed(keepass.databaseHash); - return groups; - } else { - console.log('getDatabaseGroups rejected'); - return []; - } - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); - return []; + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { + groups = response.groups; + groups.defaultGroup = page.settings.defaultGroup; + groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk; + keepass.updateLastUsed(keepass.databaseHash); + return groups; } browserAction.showDefault(tab); return []; } catch (err) { - console.log('getDatabaseGroups failed: ', err); + console.log(`${EXTENSION_NAME}: getDatabaseGroups failed: ${err}`); return []; } }; @@ -769,41 +541,25 @@ keepass.createNewGroup = async function(tab, args = []) { } const kpAction = kpActions.CREATE_NEW_GROUP; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const messageData = { action: kpAction, groupName: groupName }; - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return []; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - - if (keepass.verifyResponse(parsed, incrementedNonce)) { - keepass.updateLastUsed(keepass.databaseHash); - return parsed; - } - - console.log('getDatabaseGroups rejected'); - return []; - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); - return []; + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { + keepass.updateLastUsed(keepass.databaseHash); + return response; + } else { + console.log(`${EXTENSION_NAME}: getDatabaseGroups rejected`); } browserAction.showDefault(tab); return []; } catch (err) { - console.log('createNewGroup failed: ', err); + console.log(`${EXTENSION_NAME}: createNewGroup failed: ${err}`); return []; } }; @@ -820,7 +576,7 @@ keepass.getTotp = async function(tab, args = []) { } const kpAction = kpActions.GET_TOTP; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const messageData = { action: kpAction, @@ -828,28 +584,15 @@ keepass.getTotp = async function(tab, args = []) { }; try { - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - if (keepass.verifyResponse(parsed, incrementedNonce) && parsed.totp) { - keepass.updateLastUsed(keepass.databaseHash); - return parsed.totp; - } - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + if (response) { + keepass.updateLastUsed(keepass.databaseHash); + return response.totp; } return; } catch (err) { - console.log('getTotp failed: ', err); + console.log(`${EXTENSION_NAME}: getTotp failed: ${err}`); } }; @@ -860,7 +603,7 @@ keepass.requestAutotype = async function(tab, args = []) { } const kpAction = kpActions.REQUEST_AUTOTYPE; - const [ nonce, incrementedNonce ] = keepass.getNonces(); + const nonce = keepassClient.getNonce(); const search = getTopLevelDomainFromUrl(args[0]); const messageData = { @@ -868,56 +611,18 @@ keepass.requestAutotype = async function(tab, args = []) { search: search }; - const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID); - try { - const response = await keepass.sendNativeMessage(request); - if (response.message && response.nonce) { - const res = keepass.decrypt(response.message, response.nonce); - if (!res) { - keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); - return false; - } - - const message = nacl.util.encodeUTF8(res); - const parsed = JSON.parse(message); - - if (keepass.verifyResponse(parsed, incrementedNonce)) { - return true; - } - } else if (response.error && response.errorCode) { - keepass.handleError(tab, response.errorCode, response.error); - } - - return false; + const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce); + return response; } catch (err) { - console.log('requestAutotype failed: ', err); + console.log(`${EXTENSION_NAME}: requestAutotype failed: ${err}`); return false; } }; - -keepass.generateNewKeyPair = function() { - keepass.keyPair = nacl.box.keyPair(); - //console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey)); -}; - -keepass.isConfigured = async function() { - if (typeof(keepass.databaseHash) === 'undefined') { - const hash = keepass.getDatabaseHash(); - return hash in keepass.keyRing; - } - - return keepass.databaseHash in keepass.keyRing; -}; - -keepass.checkDatabaseHash = async function(tab) { - return keepass.databaseHash; -}; - -keepass.isAssociated = function() { - return (keepass.associated.value && keepass.associated.hash && keepass.associated.hash === keepass.databaseHash); -}; +//-------------------------------------------------------------------------- +// Keyring +//-------------------------------------------------------------------------- keepass.migrateKeyRing = function() { return new Promise((resolve, reject) => { @@ -993,6 +698,102 @@ keepass.deleteKey = function(hash) { browser.storage.local.set({ 'keyRing': keepass.keyRing }); }; +keepass.getCryptoKey = function() { + let dbkey = null; + let dbid = null; + if (!(keepass.databaseHash in keepass.keyRing)) { + return [ dbid, dbkey ]; + } + + dbid = keepass.keyRing[keepass.databaseHash].id; + + if (dbid) { + dbkey = keepass.keyRing[keepass.databaseHash].key; + } + + return [ dbid, dbkey ]; +}; + +keepass.setCryptoKey = function(id, key) { + keepass.saveKey(keepass.databaseHash, id, key); +}; + +//-------------------------------------------------------------------------- +// Connection +//-------------------------------------------------------------------------- + +keepass.enableAutomaticReconnect = function() { + // Disable for Windows if KeePassXC is older than 2.3.4 + if (!page.settings.autoReconnect + || (navigator.platform.toLowerCase().includes('win') + && keepass.currentKeePassXC + && !keepass.compareVersion('2.3.4', keepass.currentKeePassXC))) { + return; + } + + if (keepass.reconnectLoop === null) { + keepass.reconnectLoop = setInterval(async () => { + if (!keepass.isKeePassXCAvailable) { + keepass.reconnect(); + } + }, 1000); + } +}; + +keepass.disableAutomaticReconnect = function() { + clearInterval(keepass.reconnectLoop); + keepass.reconnectLoop = null; +}; + +keepass.reconnect = async function(tab, connectionTimeout) { + keepassClient.connectToNative(); + keepass.generateNewKeyPair(); + const keyChangeResult = await keepass.changePublicKeys(tab, true, connectionTimeout).catch((e) => { + return false; + }); + + // Change public keys timeout + if (!keyChangeResult) { + return false; + } + + const hash = await keepass.getDatabaseHash(tab); + if (hash !== '' && tab && page.tabs[tab.id]) { + delete page.tabs[tab.id].errorMessage; + } + + await keepass.testAssociation(); + await keepass.isConfigured(); + keepass.updateDatabaseHashToContent(); + return true; +}; + +//-------------------------------------------------------------------------- +// Utils +//-------------------------------------------------------------------------- + +keepass.generateNewKeyPair = function() { + keepass.keyPair = nacl.box.keyPair(); + //console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey)); +}; + +keepass.isConfigured = async function() { + if (typeof(keepass.databaseHash) === 'undefined') { + const hash = keepass.getDatabaseHash(); + return hash in keepass.keyRing; + } + + return keepass.databaseHash in keepass.keyRing; +}; + +keepass.checkDatabaseHash = async function(tab) { + return keepass.databaseHash; +}; + +keepass.isAssociated = function() { + return (keepass.associated.value && keepass.associated.hash && keepass.associated.hash === keepass.databaseHash); +}; + keepass.setcurrentKeePassXCVersion = function(version) { if (version) { keepass.currentKeePassXC = version; @@ -1031,8 +832,8 @@ keepass.checkForNewKeePassXCVersion = function() { } }; - xhr.onerror = function(e) { - console.log('checkForNewKeePassXCVersion error:' + e); + xhr.onerror = function(err) { + console.log(`${EXTENSION_NAME}: checkForNewKeePassXCVersion error: ${err}`); }; try { @@ -1044,239 +845,16 @@ keepass.checkForNewKeePassXCVersion = function() { keepass.latestKeePassXC.lastChecked = new Date().valueOf(); }; -keepass.connectToNative = function() { - if (keepass.nativePort) { - keepass.nativePort.disconnect(); - } - keepass.nativeConnect(); -}; - -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.updateDatabase(); - } -}; - -function onDisconnected() { - keepass.nativePort = null; - keepass.isConnected = false; - keepass.isDatabaseClosed = true; - keepass.isKeePassXCAvailable = false; - keepass.associated.value = false; - keepass.associated.hash = null; - keepass.databaseHash = ''; - page.clearCredentials(page.currentTabId, true); - keepass.updatePopup('cross'); - keepass.updateDatabaseHashToContent(); - console.log('Failed to connect: ' + (browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)); -} - -keepass.getNonce = function() { - return nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize)); -}; - -keepass.incrementedNonce = function(nonce) { - const oldNonce = nacl.util.decodeBase64(nonce); - const newNonce = oldNonce.slice(0); - - // from libsodium/utils.c - let i = 0; - let c = 1; - for (; i < newNonce.length; ++i) { - c += newNonce[i]; - newNonce[i] = c; - c >>= 8; - } - - return nacl.util.encodeBase64(newNonce); -}; - -keepass.getNonces = function() { - const nonce = keepass.getNonce(); - const incrementedNonce = keepass.incrementedNonce(nonce); - return [ nonce, incrementedNonce ]; -}; - -keepass.nativeConnect = function() { - console.log('Connecting to native messaging host ' + keepass.nativeHostName); - keepass.nativePort = browser.runtime.connectNative(keepass.nativeHostName); - keepass.nativePort.onMessage.addListener(keepass.onNativeMessage); - keepass.nativePort.onDisconnect.addListener(onDisconnected); - keepass.isConnected = true; - return keepass.nativePort; -}; - -keepass.verifyKeyResponse = function(response, key, nonce) { - if (!response.success || !response.publicKey) { - keepass.associated.hash = null; - return false; - } - - if (!keepass.checkNonceLength(response.nonce)) { - console.log('Error: Invalid nonce length'); - return false; - } - - const reply = (response.nonce === nonce); - if (response.publicKey && reply) { - keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey); - return true; - } - - return reply; -}; - -keepass.verifyResponse = function(response, nonce, id) { - keepass.associated.value = response.success; - if (response.success !== 'true') { - keepass.associated.hash = null; - return false; - } - - keepass.associated.hash = keepass.databaseHash; - - if (!keepass.checkNonceLength(response.nonce)) { - return false; - } - - keepass.associated.value = (response.nonce === nonce); - if (keepass.associated.value === false) { - console.log('Error: Nonce compare failed'); - return false; - } - - if (id) { - keepass.associated.value = (keepass.associated.value && id === response.id); - } - - keepass.associated.hash = (keepass.associated.value) ? keepass.databaseHash : null; - return keepass.isAssociated(); -}; - -keepass.verifyDatabaseResponse = function(response, nonce) { - if (response.success !== 'true') { - keepass.associated.hash = null; - return false; - } - - if (!keepass.checkNonceLength(response.nonce)) { - console.log('Error: Invalid nonce length'); - return false; - } - - if (response.nonce !== nonce) { - console.log('Error: Nonce compare failed'); - return false; - } - - keepass.associated.hash = response.hash; - return response.hash !== '' && response.success === 'true'; -}; - -keepass.checkNonceLength = function(nonce) { - return nacl.util.decodeBase64(nonce).length === nacl.secretbox.nonceLength; -}; - keepass.handleError = function(tab, errorCode, errorMessage = '') { if (errorMessage.length === 0) { errorMessage = kpErrors.getError(errorCode); } - console.log('Error ' + errorCode + ': ' + errorMessage); + console.log(`${EXTENSION_NAME}: Error ${errorCode}: ${errorMessage}`); if (tab && page.tabs[tab.id]) { page.tabs[tab.id].errorMessage = errorMessage; } }; -keepass.getCryptoKey = function() { - let dbkey = null; - let dbid = null; - if (!(keepass.databaseHash in keepass.keyRing)) { - return [ dbid, dbkey ]; - } - - dbid = keepass.keyRing[keepass.databaseHash].id; - - if (dbid) { - dbkey = keepass.keyRing[keepass.databaseHash].key; - } - - return [ dbid, dbkey ]; -}; - -keepass.setCryptoKey = function(id, key) { - keepass.saveKey(keepass.databaseHash, id, key); -}; - -keepass.encrypt = function(input, nonce) { - const messageData = nacl.util.decodeUTF8(JSON.stringify(input)); - const messageNonce = nacl.util.decodeBase64(nonce); - - if (keepass.serverPublicKey) { - const message = nacl.box(messageData, messageNonce, keepass.serverPublicKey, keepass.keyPair.secretKey); - if (message) { - return nacl.util.encodeBase64(message); - } - } - - return ''; -}; - -keepass.decrypt = function(input, nonce) { - const m = nacl.util.decodeBase64(input); - const n = nacl.util.decodeBase64(nonce); - const res = nacl.box.open(m, n, keepass.serverPublicKey, keepass.keyPair.secretKey); - return res; -}; - -keepass.enableAutomaticReconnect = function() { - // Disable for Windows if KeePassXC is older than 2.3.4 - if (!page.settings.autoReconnect - || (navigator.platform.toLowerCase().includes('win') - && keepass.currentKeePassXC - && !keepass.compareVersion('2.3.4', keepass.currentKeePassXC))) { - return; - } - - if (keepass.reconnectLoop === null) { - keepass.reconnectLoop = setInterval(async () => { - if (!keepass.isKeePassXCAvailable) { - keepass.reconnect(); - } - }, 1000); - } -}; - -keepass.disableAutomaticReconnect = function() { - clearInterval(keepass.reconnectLoop); - keepass.reconnectLoop = null; -}; - -keepass.reconnect = async function(tab, connectionTimeout) { - keepass.connectToNative(); - keepass.generateNewKeyPair(); - const keyChangeResult = await keepass.changePublicKeys(tab, true, connectionTimeout).catch((e) => { - return false; - }); - - // Change public keys timeout - if (!keyChangeResult) { - return false; - } - - const hash = await keepass.getDatabaseHash(tab); - if (hash !== '' && tab && page.tabs[tab.id]) { - delete page.tabs[tab.id].errorMessage; - } - - await keepass.testAssociation(); - await keepass.isConfigured(); - keepass.updateDatabaseHashToContent(); - return true; -}; - keepass.updatePopup = function(iconType) { if (page && page.tabs.length > 0) { browserAction.updateIcon(undefined, iconType); @@ -1288,8 +866,10 @@ keepass.updateDatabase = async function() { keepass.associated.value = false; keepass.associated.hash = null; page.clearAllLogins(); + await keepass.testAssociation(null, [ true ]); const configured = await keepass.isConfigured(); + keepass.updatePopup(configured ? 'normal' : 'locked'); keepass.updateDatabaseHashToContent(); }; @@ -1304,12 +884,12 @@ keepass.updateDatabaseHashToContent = async function() { hash: { old: keepass.previousDatabaseHash, new: keepass.databaseHash }, connected: keepass.isKeePassXCAvailable }).catch((err) => { - console.log('Error: No content script available for this tab.'); + console.log(`${EXTENSION_NAME}: Error. No content script available for this tab.`); }); keepass.previousDatabaseHash = keepass.databaseHash; } } catch (err) { - console.log('updateDatabaseHashToContent failed: ', err); + console.log(`${EXTENSION_NAME}: updateDatabaseHashToContent failed: ${err}`); } }; @@ -1329,25 +909,6 @@ keepass.compareVersion = function(minimum, current, canBeEqual = true) { return (canBeEqual ? (min <= cur) : (min < cur)); }; -keepass.buildRequest = function(action, encrypted, nonce, clientID, triggerUnlock = false) { - const request = { - action: action, - message: encrypted, - nonce: nonce, - clientID: clientID - }; - - if (triggerUnlock) { - request.triggerUnlock = 'true'; - } - - return request; -}; - -keepass.getIsKeePassXCAvailable = async function() { - return keepass.isKeePassXCAvailable; -}; - const removeDuplicateEntries = function(arr) { const newArray = []; diff --git a/keepassxc-browser/common/global.js b/keepassxc-browser/common/global.js index 2f8ffd7..9eef5fc 100755 --- a/keepassxc-browser/common/global.js +++ b/keepassxc-browser/common/global.js @@ -1,5 +1,7 @@ 'use strict'; +const EXTENSION_NAME = 'KeePassXC-Browser'; + // Site Preferences ignore options const IGNORE_NOTHING = 'ignoreNothing'; const IGNORE_NORMAL = 'ignoreNormal'; diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json index a4d73c2..001c401 100755 --- a/keepassxc-browser/manifest.json +++ b/keepassxc-browser/manifest.json @@ -36,6 +36,7 @@ "common/sites.js", "background/nacl.min.js", "background/nacl-util.min.js", + "background/client.js", "background/keepass.js", "background/httpauth.js", "background/browserAction.js",