mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Refactor background scripts for 1.8.0 (#1520)
Refactor background scripts for 1.8.0
This commit is contained in:
parent
7c8d9c3852
commit
4a0cf3abce
6 changed files with 562 additions and 656 deletions
|
|
@ -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,
|
||||
|
|
|
|||
335
keepassxc-browser/background/client.js
Normal file
335
keepassxc-browser/background/client.js
Normal file
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const EXTENSION_NAME = 'KeePassXC-Browser';
|
||||
|
||||
// Site Preferences ignore options
|
||||
const IGNORE_NOTHING = 'ignoreNothing';
|
||||
const IGNORE_NORMAL = 'ignoreNormal';
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue