Refactor background scripts for 1.8.0 (#1520)

Refactor background scripts for 1.8.0
This commit is contained in:
Sami Vänttinen 2022-01-12 11:49:33 +02:00 committed by GitHub
parent 7c8d9c3852
commit 4a0cf3abce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 562 additions and 656 deletions

View file

@ -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,

View 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();
}
};

View file

@ -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

View file

@ -1,5 +1,7 @@
'use strict';
const EXTENSION_NAME = 'KeePassXC-Browser';
// Site Preferences ignore options
const IGNORE_NOTHING = 'ignoreNothing';
const IGNORE_NORMAL = 'ignoreNormal';

View file

@ -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",