mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
New draft
This commit is contained in:
parent
76b90dbe6d
commit
8639ab2288
14 changed files with 1392 additions and 1153 deletions
|
|
@ -115,6 +115,7 @@
|
|||
"jQuery": true,
|
||||
"keepass": true,
|
||||
"keepassClient": true,
|
||||
"keepassProtocol": true,
|
||||
"kpActions": true,
|
||||
"kpErrors": true,
|
||||
"kpxc": true,
|
||||
|
|
@ -149,6 +150,8 @@
|
|||
"MIN_TOTP_INPUT_LENGTH": true,
|
||||
"nacl": true,
|
||||
"page": true,
|
||||
"protocol": true,
|
||||
"protocolClient": true,
|
||||
"Pixels": true,
|
||||
"PREDEFINED_SITELIST": true,
|
||||
"resizePopup": true,
|
||||
|
|
|
|||
|
|
@ -1,468 +0,0 @@
|
|||
'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;
|
||||
},
|
||||
|
||||
matchAndRemoveV2(msg) {
|
||||
for (let i = 0; i < this.buffer.length; ++i) {
|
||||
if (msg?.requestID === this.buffer[i].requestID) {
|
||||
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) {
|
||||
// If the request has a separate requestID, check if it matches when there's no nonce (an error message)
|
||||
const isNotificationOrError = !msg.nonce && request.requestID === msg.requestID;
|
||||
|
||||
// Only resolve a matching response or a notification (without nonce)
|
||||
if (isNotificationOrError || messageBuffer.matchAndRemoveV2(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);
|
||||
if (messageData.requestID) {
|
||||
request['requestID'] = messageData.requestID;
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
const incrementedNonce = keepassClient.incrementedNonce(nonce);
|
||||
|
||||
return keepassClient.handleResponse(response, incrementedNonce, tab);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Protocol V2
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.sendNativeMessageV2 = function(requestAction, request, enableTimeout = false, timeoutValue) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout;
|
||||
const ev = keepassClient.nativePort.onMessage;
|
||||
|
||||
const listener = ((port) => {
|
||||
const handler = (msg) => {
|
||||
if (msg && msg?.requestID === request.requestID) {
|
||||
// Only resolve a matching response
|
||||
if (messageBuffer.matchAndRemove(msg)) {
|
||||
port.removeListener(handler);
|
||||
if (enableTimeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
resolve(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev);
|
||||
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.sendMessageV2 = async function(kpAction, tab, messageData, nonce, enableTimeout = false, triggerUnlock = false) {
|
||||
const request = keepassClient.buildRequestV2(keepassClient.encrypt(messageData, nonce), nonce, keepass.clientID, messageData.requestID, triggerUnlock);
|
||||
const response = await keepassClient.sendNativeMessageV2(kpAction, request, enableTimeout);
|
||||
const incrementedNonce = keepassClient.incrementedNonce(nonce);
|
||||
|
||||
return keepassClient.handleResponseV2(response, incrementedNonce, tab);
|
||||
};
|
||||
|
||||
keepassClient.buildRequestV2 = function(encryptedMessage, nonce, clientID, requestID, triggerUnlock = false) {
|
||||
const request = {
|
||||
message: encryptedMessage,
|
||||
nonce: nonce,
|
||||
clientID: clientID,
|
||||
requestID: requestID
|
||||
};
|
||||
|
||||
if (triggerUnlock) {
|
||||
request.triggerUnlock = 'true';
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
keepassClient.handleResponseV2 = 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.verifyResponseV2(parsed, incrementedNonce)) {
|
||||
return parsed;
|
||||
}
|
||||
} else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
keepassClient.verifyResponseV2 = function(response, nonce) {
|
||||
if (!keepassClient.checkNonceLength(response.nonce)) {
|
||||
logError('Incorrect nonce length');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('Nonce compare failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.getNonce = function() {
|
||||
return nacl.util.encodeBase64(nacl.randomBytes(keepassClient.keySize));
|
||||
};
|
||||
|
||||
// Creates a random 8 character string for Request ID
|
||||
keepassClient.getRequestId = function() {
|
||||
return Math.random().toString(16).substring(2, 10);
|
||||
};
|
||||
|
||||
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)) {
|
||||
logError('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) {
|
||||
logError('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)) {
|
||||
logError('Invalid nonce length.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('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.clearAllLogins();
|
||||
keepass.updatePopup('cross');
|
||||
keepass.updateDatabaseHashToContent();
|
||||
logError(`Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)}`);
|
||||
}
|
||||
|
||||
keepassClient.onNativeMessage = function(response) {
|
||||
// Handle database lock/unlock status
|
||||
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
|
||||
keepass.updateDatabase();
|
||||
}
|
||||
};
|
||||
|
|
@ -15,7 +15,7 @@ kpxcEvent.onMessage = async function(request, sender) {
|
|||
|
||||
kpxcEvent.showStatus = async function(tab, configured, internalPoll) {
|
||||
let keyId = null;
|
||||
if (configured && keepass.databaseHash !== '') {
|
||||
if (configured && keepass.databaseHash !== '' && keepass.keyRing[keepass.databaseHash]) {
|
||||
keyId = keepass.keyRing[keepass.databaseHash].id;
|
||||
}
|
||||
|
||||
|
|
@ -27,16 +27,17 @@ kpxcEvent.showStatus = async function(tab, configured, internalPoll) {
|
|||
const usernameFieldDetected = page.tabs[tab.id]?.usernameFieldDetected ?? false;
|
||||
|
||||
return {
|
||||
identifier: keyId,
|
||||
associated: keepass.isAssociated(),
|
||||
configured: configured,
|
||||
databaseClosed: keepass.isDatabaseClosed,
|
||||
keePassXCAvailable: keepass.isKeePassXCAvailable,
|
||||
databaseAssociationStatuses: keepass.databaseAssosiationStatuses,
|
||||
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
|
||||
associated: keepass.isAssociated(),
|
||||
error: errorMessage,
|
||||
usernameFieldDetected: usernameFieldDetected,
|
||||
error: errorMessage || null,
|
||||
identifier: keyId,
|
||||
keePassXCAvailable: keepass.isKeePassXCAvailable,
|
||||
showGettingStartedGuideAlert: page.settings.showGettingStartedGuideAlert,
|
||||
showTroubleshootingGuideAlert: page.settings.showTroubleshootingGuideAlert
|
||||
showTroubleshootingGuideAlert: page.settings.showTroubleshootingGuideAlert,
|
||||
usernameFieldDetected: usernameFieldDetected
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ kpxcEvent.onLoadKeyRing = async function() {
|
|||
});
|
||||
|
||||
keepass.keyRing = item.keyRing;
|
||||
// TODO: What to do here?
|
||||
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
|
||||
keepass.associated = {
|
||||
value: false,
|
||||
|
|
@ -73,14 +75,22 @@ kpxcEvent.onGetStatus = async function(tab, args = []) {
|
|||
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
|
||||
try {
|
||||
const [ internalPoll = false, triggerUnlock = false ] = args;
|
||||
if (!internalPoll) {
|
||||
const response = await keepass.testAssociation(tab, [ true, triggerUnlock ]);
|
||||
if (!response) {
|
||||
return kpxcEvent.showStatus(tab, false);
|
||||
let configured = false;
|
||||
|
||||
if (keepass.protocolV2) {
|
||||
const response = await protocol.testAssociationFromDatabaseStatuses(tab, [ true, triggerUnlock ]);
|
||||
configured = response.isAnyAssociated;
|
||||
} else {
|
||||
if (!internalPoll) {
|
||||
const response = await keepassProtocol.testAssociation(tab, [ true, triggerUnlock ]);
|
||||
if (!response) {
|
||||
return kpxcEvent.showStatus(tab, false);
|
||||
}
|
||||
}
|
||||
|
||||
configured = await keepass.isConfigured();
|
||||
}
|
||||
|
||||
const configured = await keepass.isConfigured();
|
||||
return kpxcEvent.showStatus(tab, configured, internalPoll);
|
||||
} catch (err) {
|
||||
logError('No status shown: ' + err);
|
||||
|
|
@ -125,10 +135,11 @@ kpxcEvent.onGetConnectedDatabase = async function() {
|
|||
};
|
||||
|
||||
kpxcEvent.onGetKeePassXCVersions = async function(tab) {
|
||||
if (keepass.currentKeePassXC === '') {
|
||||
await keepass.getDatabaseHash(tab);
|
||||
// TODO: Maybe this is not needed?
|
||||
/*if (keepass.currentKeePassXC === '') {
|
||||
await keepass.getDatabaseHash(tab); // TODO: How to get just the version? A separate API call?
|
||||
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
||||
}
|
||||
}*/
|
||||
|
||||
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
||||
};
|
||||
|
|
@ -227,11 +238,11 @@ kpxcEvent.sendBackToTabs = async function(tab, args = []) {
|
|||
|
||||
// All methods named in this object have to be declared BEFORE this!
|
||||
kpxcEvent.messageHandlers = {
|
||||
'add_credentials': keepass.addCredentials,
|
||||
'associate': keepass.associate,
|
||||
'check_database_hash': keepass.checkDatabaseHash,
|
||||
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
|
||||
'compare_version': kpxcEvent.compareVersion,
|
||||
'create_credentials': keepass.createCredentials,
|
||||
'create_new_group': keepass.createNewGroup,
|
||||
'enable_automatic_reconnect': keepass.enableAutomaticReconnect,
|
||||
'disable_automatic_reconnect': keepass.disableAutomaticReconnect,
|
||||
|
|
@ -240,7 +251,7 @@ kpxcEvent.messageHandlers = {
|
|||
'generate_password': keepass.generatePassword,
|
||||
'get_color_theme': kpxcEvent.getColorTheme,
|
||||
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
|
||||
'get_database_hash': keepass.getDatabaseHash,
|
||||
'get_database_hash': keepass.getDatabaseHash, // TODO ?
|
||||
'get_database_groups': keepass.getDatabaseGroups,
|
||||
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
|
||||
'get_login_list': page.getLoginList,
|
||||
|
|
|
|||
|
|
@ -33,583 +33,98 @@ const kpActions = {
|
|||
CREATE_NEW_GROUP: 'create-new-group',
|
||||
GET_TOTP: 'get-totp',
|
||||
REQUEST_AUTOTYPE: 'request-autotype',
|
||||
// V2
|
||||
// Protocol V2
|
||||
CREATE_CREDENTIALS: 'create-credentials',
|
||||
GET_CREDENTIALS: 'get-credentials'
|
||||
GET_CREDENTIALS: 'get-credentials',
|
||||
GET_DATABASE_STATUSES: 'get-database-statuses'
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Commands
|
||||
// Command wrappers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepass.addCredentials = async function(tab, args = []) {
|
||||
const [ username, password, url, group, groupUuid ] = args;
|
||||
return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||
keepass.associate = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.associate(tab, args) : await keepassProtocol.associate(tab, args);
|
||||
};
|
||||
|
||||
keepass.updateCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
const taResponse = await keepass.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.SET_LOGIN;
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
login: username,
|
||||
password: password,
|
||||
url: url,
|
||||
submitUrl: url
|
||||
};
|
||||
|
||||
if (entryId) {
|
||||
messageData.uuid = entryId;
|
||||
}
|
||||
|
||||
if (!entryId && page.settings.downloadFaviconAfterSave) {
|
||||
messageData.downloadFavicon = 'true';
|
||||
}
|
||||
|
||||
if (group && groupUuid) {
|
||||
messageData.group = group;
|
||||
messageData.groupUuid = groupUuid;
|
||||
}
|
||||
|
||||
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 = response.error;
|
||||
if (response.error === 'success' || response.error === '') {
|
||||
successMessage = entryId ? 'updated' : 'created';
|
||||
}
|
||||
|
||||
return successMessage;
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`updateCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepass.retrieveCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||
const taResponse = await keepass.testAssociation(tab, [ false, triggerUnlock ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
const keys = [];
|
||||
const kpAction = kpActions.GET_LOGINS;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
|
||||
for (const keyHash in keepass.keyRing) {
|
||||
keys.push({
|
||||
id: keepass.keyRing[keyHash].id,
|
||||
key: keepass.keyRing[keyHash].key
|
||||
});
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
url: url,
|
||||
keys: keys
|
||||
};
|
||||
|
||||
if (submiturl) {
|
||||
messageData.submitUrl = submiturl;
|
||||
}
|
||||
|
||||
if (httpAuth) {
|
||||
messageData.httpAuth = 'true';
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
entries = keepass.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);
|
||||
}
|
||||
|
||||
logDebug(`Found ${entries.length} entries for url ${url}`);
|
||||
return entries;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`retrieveCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepass.generatePassword = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const taResponse = await keepass.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!keepass.compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let password;
|
||||
const kpAction = kpActions.GENERATE_PASSWORD;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID,
|
||||
requestID: keepassClient.getRequestId()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
password = response.entries ?? response.password;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
} else {
|
||||
logError('generatePassword rejected');
|
||||
}
|
||||
|
||||
return password;
|
||||
} catch (err) {
|
||||
logError(`generatePassword failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepass.associate = async function(tab) {
|
||||
if (keepass.isAssociated()) {
|
||||
return AssociatedAction.ASSOCIATED;
|
||||
}
|
||||
|
||||
try {
|
||||
await keepass.getDatabaseHash(tab);
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const kpAction = kpActions.ASSOCIATE;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = keepassClient.getNonce();
|
||||
const idKeyPair = nacl.box.keyPair();
|
||||
const idKey = nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
key: key,
|
||||
idKey: idKey
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
} catch (err) {
|
||||
logError(`associate failed: ${err}`);
|
||||
}
|
||||
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
};
|
||||
|
||||
keepass.testAssociation = async function(tab, args = []) {
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
try {
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const dbHash = await keepass.getDatabaseHash(tab, [ enableTimeout, triggerUnlock ]);
|
||||
if (!dbHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keepass.isAssociated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.TEST_ASSOCIATE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ dbid, dbkey ] = keepass.getCryptoKey();
|
||||
|
||||
if (dbkey === null || dbid === null) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.NO_SAVED_DATABASES_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
key: dbkey
|
||||
};
|
||||
|
||||
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;
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
|
||||
return keepass.isAssociated();
|
||||
} catch (err) {
|
||||
logError(`testAssociation failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.getDatabaseHash = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
keepass.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const kpAction = kpActions.GET_DATABASE_HASH;
|
||||
const [ nonce, incrementedNonce ] = keepassClient.getNonces();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
connectedKeys: Object.keys(keepass.keyRing) // This will be removed in the future
|
||||
};
|
||||
|
||||
const encrypted = keepassClient.encrypt(messageData, nonce);
|
||||
if (encrypted.length <= 0) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
try {
|
||||
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 = keepassClient.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 (keepassClient.verifyDatabaseResponse(parsed, incrementedNonce) && parsed.hash) {
|
||||
const oldDatabaseHash = keepass.databaseHash;
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.databaseHash = parsed.hash || '';
|
||||
|
||||
if (oldDatabaseHash && oldDatabaseHash !== keepass.databaseHash) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = false;
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
|
||||
// Update the databaseHash from legacy hash
|
||||
if (parsed.oldHash) {
|
||||
keepass.updateDatabaseHash(parsed.oldHash, parsed.hash);
|
||||
}
|
||||
|
||||
return parsed.hash;
|
||||
} else if (parsed.errorCode) {
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
if (response.message && response.message === '') {
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
} else {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
return keepass.databaseHash;
|
||||
} catch (err) {
|
||||
logError(`getDatabaseHash failed: ${err}`);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.changePublicKeys = async function(tab, enableTimeout = false, connectionTimeout) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const [ nonce, incrementedNonce ] = keepassClient.getNonces();
|
||||
keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepassClient.keySize));
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
publicKey: key,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout, connectionTimeout);
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
|
||||
if (!keepassClient.verifyKeyResponse(response, key, incrementedNonce)) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
}
|
||||
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
console.log(`${EXTENSION_NAME}: Server public key: ${nacl.util.encodeBase64(keepass.serverPublicKey)}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
logError(`changePublicKeys failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.lockDatabase = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
logError(`ockDatabase failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.getDatabaseGroups = async function(tab) {
|
||||
try {
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let groups = [];
|
||||
const kpAction = kpActions.GET_DATABASE_GROUPS;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
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) {
|
||||
logError(`getDatabaseGroups failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
keepass.createCredentials = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.createCredentials(tab, args) : await keepassProtocol.addCredentials(tab, args);
|
||||
};
|
||||
|
||||
keepass.createNewGroup = async function(tab, args = []) {
|
||||
try {
|
||||
const [ groupName ] = args;
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
return keepass.protocolV2 ? await protocol.createNewGroup(tab, args) : await keepassProtocol.createNewGroup(tab, args);
|
||||
};
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
keepass.getCredentials = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.getCredentials(tab, args) : await keepassProtocol.retrieveCredentials(tab, args);
|
||||
};
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
keepass.getDatabaseGroups = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.getDatabaseGroups(tab, args) : await keepassProtocol.getDatabaseGroups(tab, args);
|
||||
};
|
||||
|
||||
const kpAction = kpActions.CREATE_NEW_GROUP;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
groupName: groupName
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response;
|
||||
} else {
|
||||
logError('getDatabaseGroups rejected');
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`createNewGroup failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
keepass.getDatabaseHash = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.getDatabaseStatuses(tab, args) : await keepassProtocol.getDatabaseHash(tab, args);
|
||||
};
|
||||
|
||||
keepass.getTotp = async function(tab, args = []) {
|
||||
const [ uuid, oldTotp ] = args;
|
||||
if (!keepass.compareVersion('2.6.1', keepass.currentKeePassXC, true)) {
|
||||
return oldTotp;
|
||||
}
|
||||
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_TOTP;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
uuid: uuid
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response.totp;
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
logError(`getTotp failed: ${err}`);
|
||||
}
|
||||
return keepass.protocolV2 ? await protocol.getTotp(tab, args) : await keepassProtocol.getTotp(tab, args);
|
||||
};
|
||||
|
||||
keepass.requestAutotype = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
keepass.lockDatabase = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.lockDatabase(tab, args) : await keepassProtocol.lockDatabase(tab, args);
|
||||
};
|
||||
|
||||
const kpAction = kpActions.REQUEST_AUTOTYPE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const search = getTopLevelDomainFromUrl(args[0]);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
search: search
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
return response;
|
||||
} catch (err) {
|
||||
logError(`requestAutotype failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
keepass.updateCredentials = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.updateCredentials(tab, args) : await keepassProtocol.updateCredentials(tab, args);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
|
@ -739,34 +254,55 @@ keepass.disableAutomaticReconnect = function() {
|
|||
};
|
||||
|
||||
keepass.reconnect = async function(tab, connectionTimeout) {
|
||||
keepassClient.connectToNative();
|
||||
keepass.generateNewKeyPair();
|
||||
const keyChangeResult = await keepass.changePublicKeys(tab, true, connectionTimeout).catch(() => false);
|
||||
protocolClient.connectToNative();
|
||||
protocolClient.generateNewKeyPair();
|
||||
const keyChangeResult = await protocol.changePublicKeys(tab, true, connectionTimeout).catch(() => false);
|
||||
|
||||
// Change public keys timeout
|
||||
if (!keyChangeResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hash = await keepass.getDatabaseHash(tab);
|
||||
if (hash !== '') {
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
// What to do here?
|
||||
if (!keepass.protocolV2) {
|
||||
// Needed?
|
||||
const hash = await keepass.getDatabaseHash(tab);
|
||||
if (hash !== '') {
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
|
||||
await keepass.testAssociation();
|
||||
await keepass.isConfigured();
|
||||
await keepass.testAssociation();
|
||||
await keepass.isConfigured();
|
||||
}
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return true;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
// Error handling
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepass.generateNewKeyPair = function() {
|
||||
keepass.keyPair = nacl.box.keyPair();
|
||||
keepass.clearErrorMessage = function(tab) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.handleError = function(tab, errorCode, errorMessage = '') {
|
||||
if (errorMessage.length === 0) {
|
||||
errorMessage = kpErrors.getError(errorCode);
|
||||
}
|
||||
|
||||
logError(`${errorCode}: ${errorMessage}`);
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = errorMessage;
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepass.isConfigured = async function() {
|
||||
if (typeof(keepass.databaseHash) === 'undefined') {
|
||||
const hash = keepass.getDatabaseHash();
|
||||
|
|
@ -787,7 +323,6 @@ keepass.isAssociated = function() {
|
|||
keepass.setcurrentKeePassXCVersion = function(version) {
|
||||
if (version) {
|
||||
keepass.currentKeePassXC = version;
|
||||
keepass.protocolV2 = keepass.compareVersion('2.8.0', version);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -837,23 +372,6 @@ keepass.checkForNewKeePassXCVersion = function() {
|
|||
keepass.latestKeePassXC.lastChecked = new Date().valueOf();
|
||||
};
|
||||
|
||||
keepass.clearErrorMessage = function(tab) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.handleError = function(tab, errorCode, errorMessage = '') {
|
||||
if (errorMessage.length === 0) {
|
||||
errorMessage = kpErrors.getError(errorCode);
|
||||
}
|
||||
|
||||
logError(`${errorCode}: ${errorMessage}`);
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = errorMessage;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.updatePopup = function() {
|
||||
if (page && page.tabs.length > 0) {
|
||||
browserAction.showDefault();
|
||||
|
|
@ -866,19 +384,29 @@ keepass.updateDatabase = async function() {
|
|||
keepass.associated.hash = null;
|
||||
page.clearAllLogins();
|
||||
|
||||
await keepass.testAssociation(null, [ true ]);
|
||||
if (keepass.protocolV2) {
|
||||
// TODO: Only show "Connect" if the active database is not connected?
|
||||
// TODO: What if there are credentials from another database but the selected one is not connected?
|
||||
const result = await protocol.testAssociationFromDatabaseStatuses();
|
||||
keepass.updatePopup();
|
||||
keepass.updateDatabaseHashToContent(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy protocol
|
||||
await keepassProtocol.testAssociation(null, [ true ]);
|
||||
keepass.updatePopup();
|
||||
keepass.updateDatabaseHashToContent();
|
||||
};
|
||||
|
||||
keepass.updateDatabaseHashToContent = async function() {
|
||||
keepass.updateDatabaseHashToContent = async function(associateResult = {}) {
|
||||
try {
|
||||
const tab = await getCurrentTab();
|
||||
if (tab) {
|
||||
// Send message to content script
|
||||
browser.tabs.sendMessage(tab.id, {
|
||||
action: 'check_database_hash',
|
||||
associateResult: associateResult,
|
||||
hash: { old: keepass.previousDatabaseHash, new: keepass.databaseHash },
|
||||
connected: keepass.isKeePassXCAvailable
|
||||
}).catch((err) => {
|
||||
|
|
|
|||
576
keepassxc-browser/background/legacyProtocol.js
Normal file
576
keepassxc-browser/background/legacyProtocol.js
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
'use strict';
|
||||
|
||||
// Legacy protocol, "old" client for KeePassXC 2.7.5 and older.
|
||||
const keepassProtocol = {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Commands
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassProtocol.addCredentials = async function(tab, args = []) {
|
||||
const [ username, password, url, group, groupUuid ] = args;
|
||||
return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||
};
|
||||
|
||||
keepassProtocol.associate = async function(tab) {
|
||||
if (keepass.isAssociated()) {
|
||||
return AssociatedAction.ASSOCIATED;
|
||||
}
|
||||
|
||||
try {
|
||||
await keepassProtocol.getDatabaseHash(tab);
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const kpAction = kpActions.ASSOCIATE;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = protocolClient.getNonce();
|
||||
const idKeyPair = nacl.box.keyPair();
|
||||
const idKey = nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
key: key,
|
||||
idKey: idKey
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
} catch (err) {
|
||||
logError(`associate failed: ${err}`);
|
||||
}
|
||||
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
};
|
||||
|
||||
keepassProtocol.createNewGroup = async function(tab, args = []) {
|
||||
try {
|
||||
const [ groupName ] = args;
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CREATE_NEW_GROUP;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
groupName: groupName
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response;
|
||||
} else {
|
||||
logError('getDatabaseGroups rejected');
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`createNewGroup failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
keepassProtocol.changePublicKeys = async function(tab, enableTimeout = false, connectionTimeout) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const [ nonce, incrementedNonce ] = protocolClient.getNonces();
|
||||
keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(protocolClient.keySize));
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
publicKey: key,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout, connectionTimeout);
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
|
||||
if (!keepassClient.verifyKeyResponse(response, key, incrementedNonce)) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
}
|
||||
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
console.log(`${EXTENSION_NAME}: Server public key: ${nacl.util.encodeBase64(keepass.serverPublicKey)}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
logError(`changePublicKeys failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.generatePassword = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const taResponse = await keepassProtocol.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!keepass.compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let password;
|
||||
const kpAction = kpActions.GENERATE_PASSWORD;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID,
|
||||
requestID: protocolClient.getRequestId()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
password = response.entries ?? response.password;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
} else {
|
||||
logError('generatePassword rejected');
|
||||
}
|
||||
|
||||
return password;
|
||||
} catch (err) {
|
||||
logError(`generatePassword failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.getDatabaseGroups = async function(tab) {
|
||||
try {
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let groups = [];
|
||||
const kpAction = kpActions.GET_DATABASE_GROUPS;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
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) {
|
||||
logError(`getDatabaseGroups failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
keepassProtocol.getDatabaseHash = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
keepassProtocol.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const kpAction = kpActions.GET_DATABASE_HASH;
|
||||
const [ nonce, incrementedNonce ] = protocolClient.getNonces();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
connectedKeys: Object.keys(keepass.keyRing) // This will be removed in the future
|
||||
};
|
||||
|
||||
// Why is this here?
|
||||
/*const encrypted = protocolClient.encrypt(messageData, nonce);
|
||||
if (encrypted.length <= 0) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return keepass.databaseHash;
|
||||
}*/
|
||||
|
||||
try {
|
||||
const request = keepassClient.buildRequest(kpAction, protocolClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
if (response.message && response.nonce) {
|
||||
const res = protocolClient.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 (keepassClient.verifyDatabaseResponse(parsed, incrementedNonce) && parsed.hash) {
|
||||
const oldDatabaseHash = keepass.databaseHash;
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.databaseHash = parsed.hash || '';
|
||||
|
||||
if (oldDatabaseHash && oldDatabaseHash !== keepass.databaseHash) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = false;
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
|
||||
// Update the databaseHash from legacy hash
|
||||
if (parsed.oldHash) {
|
||||
keepass.updateDatabaseHash(parsed.oldHash, parsed.hash);
|
||||
}
|
||||
|
||||
return parsed.hash;
|
||||
} else if (parsed.errorCode) {
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
if (response.message && response.message === '') {
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
} else {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
return keepass.databaseHash;
|
||||
} catch (err) {
|
||||
logError(`getDatabaseHash failed: ${err}`);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.getTotp = async function(tab, args = []) {
|
||||
const [ uuid, oldTotp ] = args;
|
||||
if (!keepass.compareVersion('2.6.1', keepass.currentKeePassXC, true)) {
|
||||
return oldTotp;
|
||||
}
|
||||
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_TOTP;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
uuid: uuid
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response.totp;
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
logError(`getTotp failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.lockDatabase = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
logError(`ockDatabase failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.requestAutotype = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.REQUEST_AUTOTYPE;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const search = getTopLevelDomainFromUrl(args[0]);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
search: search
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
return response;
|
||||
} catch (err) {
|
||||
logError(`requestAutotype failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.retrieveCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false, triggerUnlock ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
const keys = [];
|
||||
const kpAction = kpActions.GET_LOGINS;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
|
||||
for (const keyHash in keepass.keyRing) {
|
||||
keys.push({
|
||||
id: keepass.keyRing[keyHash].id,
|
||||
key: keepass.keyRing[keyHash].key
|
||||
});
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
url: url,
|
||||
keys: keys
|
||||
};
|
||||
|
||||
if (submiturl) {
|
||||
messageData.submitUrl = submiturl;
|
||||
}
|
||||
|
||||
if (httpAuth) {
|
||||
messageData.httpAuth = 'true';
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
entries = keepass.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);
|
||||
}
|
||||
|
||||
logDebug(`Found ${entries.length} entries for url ${url}`);
|
||||
return entries;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`retrieveCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.testAssociation = async function(tab, args = []) {
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
try {
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const dbHash = await keepassProtocol.getDatabaseHash(tab, [ enableTimeout, triggerUnlock ]);
|
||||
if (!dbHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keepass.isAssociated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.TEST_ASSOCIATE;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const [ dbid, dbkey ] = keepass.getCryptoKey();
|
||||
|
||||
if (dbkey === null || dbid === null) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.NO_SAVED_DATABASES_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
key: dbkey
|
||||
};
|
||||
|
||||
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;
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
|
||||
return keepass.isAssociated();
|
||||
} catch (err) {
|
||||
logError(`testAssociation failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.updateCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
const taResponse = await keepassProtocol.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.SET_LOGIN;
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
login: username,
|
||||
password: password,
|
||||
url: url,
|
||||
submitUrl: url
|
||||
};
|
||||
|
||||
if (entryId) {
|
||||
messageData.uuid = entryId;
|
||||
}
|
||||
|
||||
if (!entryId && page.settings.downloadFaviconAfterSave) {
|
||||
messageData.downloadFavicon = 'true';
|
||||
}
|
||||
|
||||
if (group && groupUuid) {
|
||||
messageData.group = group;
|
||||
messageData.groupUuid = groupUuid;
|
||||
}
|
||||
|
||||
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 = response.error;
|
||||
if (response.error === 'success' || response.error === '') {
|
||||
successMessage = entryId ? 'updated' : 'created';
|
||||
}
|
||||
|
||||
return successMessage;
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`updateCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
201
keepassxc-browser/background/legacyProtocolClient.js
Normal file
201
keepassxc-browser/background/legacyProtocolClient.js
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
'use strict';
|
||||
|
||||
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 === protocolClient.incrementedNonce(this.buffer[i].nonce)) {
|
||||
this.buffer.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const keepassClient = {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Messaging
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.sendNativeMessage = function(request, enableTimeout = false, timeoutValue) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout;
|
||||
const requestAction = request.action;
|
||||
const ev = protocolClient.nativePort.onMessage;
|
||||
|
||||
const listener = ((port, action) => {
|
||||
const handler = (msg) => {
|
||||
if (msg && msg?.action === action) {
|
||||
// If the request has a separate requestID, check if it matches when there's no nonce (an error message)
|
||||
const isNotificationOrError = !msg.nonce && request.requestID === msg.requestID;
|
||||
|
||||
// Only resolve a matching response or a notification (without nonce)
|
||||
if (isNotificationOrError || messageBuffer.matchAndRemove(msg)) {
|
||||
port.removeListener(handler);
|
||||
if (enableTimeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
resolve(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev, requestAction);
|
||||
ev.addListener(listener);
|
||||
|
||||
const messageTimeout = timeoutValue || protocolClient.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 (protocolClient.nativePort) {
|
||||
protocolClient.nativePort.postMessage(request);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
keepassClient.handleResponse = function(response, incrementedNonce, tab) {
|
||||
if (response.message && response.nonce) {
|
||||
const res = protocolClient.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, protocolClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
if (messageData.requestID) {
|
||||
request['requestID'] = messageData.requestID;
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
const incrementedNonce = protocolClient.incrementedNonce(nonce);
|
||||
|
||||
return keepassClient.handleResponse(response, incrementedNonce, tab);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.verifyKeyResponse = function(response, key, nonce) {
|
||||
if (!response.success || !response.publicKey) {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
logError('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 (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.value = (response.nonce === nonce);
|
||||
if (keepass.associated.value === false) {
|
||||
logError('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 (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
logError('Invalid nonce length.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('Nonce compare failed.');
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.hash = response.hash;
|
||||
return response.hash !== '' && response.success === 'true';
|
||||
};
|
||||
|
|
@ -209,7 +209,8 @@ page.retrieveCredentials = async function(tab, args = []) {
|
|||
page.currentRequest.submitUrl = submitUrl;
|
||||
}
|
||||
|
||||
const credentials = await keepass.retrieveCredentials(tab, args);
|
||||
// TODO: Make keepass.js to handle protocol/protocolClient and legacyProtocol/legacyProcotolClient
|
||||
const credentials = await keepass.getCredentials(tab, args);
|
||||
page.tabs[tab.id].credentials = credentials;
|
||||
return credentials;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Protocol V2
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const protocol = {};
|
||||
|
||||
protocol.associate = async function(tab, args = []) {
|
||||
console.log('associate');
|
||||
|
||||
if (!keepass.isKeePassXCAvailable) {
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
}
|
||||
|
|
@ -14,7 +16,6 @@ protocol.associate = async function(tab, args = []) {
|
|||
|
||||
const kpAction = kpActions.ASSOCIATE;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = keepassClient.getNonce();
|
||||
const idKeyPair = nacl.box.keyPair();
|
||||
const idKey = nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ protocol.associate = async function(tab, args = []) {
|
|||
idKey: idKey
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessageV2(kpAction, tab, messageData, nonce, false, true);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData, false, true);
|
||||
if (response && response.id && response.hash) {
|
||||
keepass.setCryptoKey(response.id, key); // Save the new identification public key as id key for the database
|
||||
|
||||
|
|
@ -42,8 +43,6 @@ protocol.associate = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
protocol.changePublicKeys = async function(tab, enableTimeout = false, connectionTimeout) {
|
||||
console.log('change-public-keys');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
|
|
@ -58,15 +57,17 @@ protocol.changePublicKeys = async function(tab, enableTimeout = false, connectio
|
|||
action: kpAction,
|
||||
publicKey: key,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
clientID: keepass.clientID,
|
||||
requestID: keepassClient.getRequestId()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendNativeMessageV2(kpAction, request, enableTimeout, connectionTimeout);
|
||||
const response = await protocolClient.sendNativeMessage(kpAction, request, enableTimeout, connectionTimeout);
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
keepass.protocolV2 = response?.protocolVersion === 2;
|
||||
|
||||
const verified = keepass.protocolV2
|
||||
? keepassClient.verifyResponseV2(response, incrementedNonce)
|
||||
? protocolClient.verifyNonce(response, incrementedNonce)
|
||||
: keepassClient.verifyKeyResponse(response, key, incrementedNonce);
|
||||
if (!response?.publicKey || !verified) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
|
|
@ -88,33 +89,30 @@ protocol.changePublicKeys = async function(tab, enableTimeout = false, connectio
|
|||
};
|
||||
|
||||
protocol.createCredentials = async function(tab, args = []) {
|
||||
console.log('create-credentials');
|
||||
|
||||
const [ username, password, url, group, groupUuid ] = args;
|
||||
return protocol.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||
};
|
||||
|
||||
protocol.createNewGroup = async function(tab, args = []) {
|
||||
console.log('create-new-group');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const [ groupName ] = args;
|
||||
const kpAction = kpActions.CREATE_NEW_GROUP;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ groupName ] = args;
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
groupName: groupName
|
||||
id: dbid,
|
||||
groupName: groupName,
|
||||
};
|
||||
|
||||
try {
|
||||
// TODO: Handle errors
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash); // ?
|
||||
return response;
|
||||
|
|
@ -131,8 +129,6 @@ protocol.createNewGroup = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
protocol.generatePassword = async function(tab, args = []) {
|
||||
console.log('generate-password');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -141,19 +137,17 @@ protocol.generatePassword = async function(tab, args = []) {
|
|||
return [];
|
||||
}
|
||||
|
||||
let password;
|
||||
const kpAction = kpActions.GENERATE_PASSWORD;
|
||||
const nonce = keepassClient.getNonce();
|
||||
let password;
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID,
|
||||
requestID: keepassClient.getRequestId()
|
||||
requestID: keepassClient.getRequestId() // Needed?
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
if (response) {
|
||||
password = response.entries ?? response.password;
|
||||
keepass.updateLastUsed(keepass.databaseHash); // ?
|
||||
|
|
@ -169,25 +163,20 @@ protocol.generatePassword = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
protocol.getCredentials = async function(tab, args = []) {
|
||||
console.log('get-credentials');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const kpAction = kpActions.GET_CREDENTIALS;
|
||||
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||
let entries = [];
|
||||
const kpAction = kpActions.GET_CREDENTIALS;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
url: url,
|
||||
keys: keepass.getKeys()
|
||||
keys: protocol.getKeys()
|
||||
};
|
||||
|
||||
if (submiturl) {
|
||||
|
|
@ -200,7 +189,7 @@ protocol.getCredentials = async function(tab, args = []) {
|
|||
|
||||
try {
|
||||
// TODO: Handle errors
|
||||
const response = await keepassClient.sendMessageV2(kpAction, tab, messageData, nonce, false, triggerUnlock);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData, false, triggerUnlock);
|
||||
if (response) {
|
||||
entries = keepass.removeDuplicateEntries(response.entries);
|
||||
keepass.updateLastUsed(keepass.databaseHash); // What about this?
|
||||
|
|
@ -223,25 +212,24 @@ protocol.getCredentials = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
protocol.getDatabaseGroups = async function(tab, args = []) {
|
||||
console.log('get-database-groups');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
let groups = [];
|
||||
const kpAction = kpActions.GET_DATABASE_GROUPS;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
let groups = [];
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
action: kpAction,
|
||||
id: dbid
|
||||
};
|
||||
|
||||
try {
|
||||
// TODO: Handle errors
|
||||
const response = await keepassClient.sendMessageV2(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
if (response) {
|
||||
groups = response.groups;
|
||||
groups.defaultGroup = page.settings.defaultGroup;
|
||||
|
|
@ -258,33 +246,72 @@ protocol.getDatabaseGroups = async function(tab, args = []) {
|
|||
}
|
||||
};
|
||||
|
||||
// Obsolete? This should be checked inside KeePassXC
|
||||
protocol.getDatabaseStatuses = async function(tab, args = []) {
|
||||
console.log('get-database-statuses');
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
await protocol.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_DATABASE_STATUSES;
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
keys: protocol.getKeys()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData, enableTimeout, triggerUnlock);
|
||||
if (response) {
|
||||
keepass.databaseHash = response.hash;
|
||||
|
||||
// Return this error only if all databases are closed
|
||||
if (response?.statuses.every(s => s.locked)) {
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// TODO: Check if these are even possible ..?
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
if (response.message && response.message === '') {
|
||||
// ..?
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
} else {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`getDatabaseStatuses failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
protocol.getTotp = async function(tab, args = []) {
|
||||
console.log('get-totp');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_TOTP;
|
||||
const [ uuid, oldTotp ] = args;
|
||||
if (!keepass.compareVersion('2.6.1', keepass.currentKeePassXC, true)) {
|
||||
return oldTotp;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_TOTP;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
uuid: uuid
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response.totp;
|
||||
|
|
@ -297,22 +324,18 @@ protocol.getTotp = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
protocol.lockDatabase = async function(tab, args = []) {
|
||||
console.log('lock-database');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessageV2(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
if (response) {
|
||||
//keepass.isDatabaseClosed = true; // ?
|
||||
keepass.updateDatabase();
|
||||
|
|
@ -332,15 +355,12 @@ protocol.lockDatabase = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
protocol.requestAutotype = async function(tab, args = []) {
|
||||
console.log('request-autotype');
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.REQUEST_AUTOTYPE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const search = getTopLevelDomainFromUrl(args[0]);
|
||||
|
||||
const messageData = {
|
||||
|
|
@ -349,7 +369,7 @@ protocol.requestAutotype = async function(tab, args = []) {
|
|||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessageV2(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
return response?.result;
|
||||
} catch (err) {
|
||||
logError(`requestAutotype failed: ${err}`);
|
||||
|
|
@ -357,6 +377,55 @@ protocol.requestAutotype = async function(tab, args = []) {
|
|||
}
|
||||
};
|
||||
|
||||
protocol.testAssociationFromDatabaseStatuses = async function(tab, args = []) {
|
||||
const databaseStatuses = await protocol.getDatabaseStatuses(tab, args);
|
||||
console.log(databaseStatuses);
|
||||
|
||||
const result = {
|
||||
areAllLocked: true,
|
||||
associationNeeded: false,
|
||||
databaseHash: undefined,
|
||||
isAnyAssociated: false
|
||||
};
|
||||
|
||||
// TODO: Handle this already in getDatabaseStatuses?
|
||||
if (!databaseStatuses || databaseStatuses.statuses.length === 0) {
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return result;
|
||||
}
|
||||
|
||||
const currentDatabaseStatus = databaseStatuses.statuses.filter(s => s.hash === databaseStatuses.hash);
|
||||
const isCurrentAssociated = currentDatabaseStatus[0]?.associated;
|
||||
const isCurrentLocked = currentDatabaseStatus[0]?.locked;
|
||||
|
||||
const isAnyAssociated = databaseStatuses.statuses.some(s => s.associated);
|
||||
const areAllLocked = databaseStatuses.statuses.every(s => s.locked);
|
||||
|
||||
// TODO: Add a warning notification if two databases with identical hashes are regognized.
|
||||
// To where? DOM? KeePassXC? Popup? Maybe this feature should be in KeePassXC instead when making the request and not here.
|
||||
if (currentDatabaseStatus.length > 1) {
|
||||
console.log('Identical databases found.');
|
||||
}
|
||||
|
||||
// TODO: If the current one is not associated, activate the Connect button in the popup?
|
||||
// But only if the current database is not locked..
|
||||
if (!isCurrentAssociated && !isCurrentLocked) {
|
||||
console.log('Current one is not associated');
|
||||
|
||||
}
|
||||
|
||||
// This should be true only if all databases are locked
|
||||
keepass.isDatabaseClosed = areAllLocked; // ?
|
||||
|
||||
result.areAllLocked = areAllLocked;
|
||||
result.associationNeeded = !isCurrentAssociated && !isCurrentLocked;
|
||||
result.databaseHash = databaseStatuses.hash;
|
||||
result.isAnyAssociated = isAnyAssociated;
|
||||
|
||||
keepass.databaseAssosiationStatuses = result;
|
||||
return result;
|
||||
};
|
||||
|
||||
protocol.updateCredentials = async function(tab, args = []) {
|
||||
console.log('update-credentials');
|
||||
|
||||
|
|
@ -364,10 +433,9 @@ protocol.updateCredentials = async function(tab, args = []) {
|
|||
return [];
|
||||
}
|
||||
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
const kpAction = kpActions.CREATE_CREDENTIALS;
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
|
|
@ -393,7 +461,7 @@ protocol.updateCredentials = async function(tab, args = []) {
|
|||
|
||||
try {
|
||||
// TODO: Check response messages
|
||||
const response = await keepassClient.sendMessageV2(kpAction, tab, messageData, nonce);
|
||||
const response = await protocolClient.sendMessage(kpAction, tab, messageData);
|
||||
if (response) {
|
||||
// KeePassXC versions lower than 2.5.0 will have an empty parsed.error
|
||||
let successMessage = response.error;
|
||||
|
|
@ -410,3 +478,20 @@ protocol.updateCredentials = async function(tab, args = []) {
|
|||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocol.getKeys = function() {
|
||||
const keys = [];
|
||||
|
||||
for (const keyHash in keepass.keyRing) {
|
||||
keys.push({
|
||||
id: keepass.keyRing[keyHash].id,
|
||||
key: keepass.keyRing[keyHash].key
|
||||
});
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
|
|
|||
258
keepassxc-browser/background/protocolClient.js
Normal file
258
keepassxc-browser/background/protocolClient.js
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
'use strict';
|
||||
|
||||
// Stores 'requestID' and 'action' to internal buffer
|
||||
const protocolBuffer = {
|
||||
buffer: [],
|
||||
|
||||
addMessage(msg) {
|
||||
if (!this.buffer.includes(msg)) {
|
||||
this.buffer.push({
|
||||
action: msg?.action,
|
||||
requestID: msg?.requestID
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
matchAndRemove(msg) {
|
||||
for (let i = 0; i < this.buffer.length; ++i) {
|
||||
if (msg?.requestID === this.buffer[i].requestID
|
||||
|| (msg.action === 'change-public-keys' && msg?.action === this.buffer[i].action)) {
|
||||
this.buffer.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Protocol V2
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const protocolClient = {};
|
||||
protocolClient.keySize = 24;
|
||||
protocolClient.messageTimeout = 500; // Milliseconds
|
||||
protocolClient.nativeHostName = 'org.keepassxc.keepassxc_browser';
|
||||
protocolClient.nativePort = null;
|
||||
|
||||
protocolClient.sendNativeMessage = function(requestAction, request, enableTimeout = false, timeoutValue) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout;
|
||||
const ev = protocolClient.nativePort.onMessage;
|
||||
|
||||
const listener = ((port) => {
|
||||
const handler = (msg) => {
|
||||
if (msg && (msg?.requestID === request.requestID || msg?.action === 'change-public-keys')) {
|
||||
// Only resolve a matching response
|
||||
if (protocolBuffer.matchAndRemove(msg)) {
|
||||
port.removeListener(handler);
|
||||
if (enableTimeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
resolve(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev);
|
||||
ev.addListener(listener);
|
||||
|
||||
const messageTimeout = timeoutValue || protocolClient.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
|
||||
protocolBuffer.addMessage(request);
|
||||
|
||||
// Send the request
|
||||
if (protocolClient.nativePort) {
|
||||
protocolClient.nativePort.postMessage(request);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
protocolClient.sendMessage = async function(kpAction, tab, messageData, enableTimeout = false, triggerUnlock = false) {
|
||||
const nonce = protocolClient.getNonce();
|
||||
const request = protocolClient.buildRequest(protocolClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
const response = await protocolClient.sendNativeMessage(kpAction, request, enableTimeout);
|
||||
const incrementedNonce = keepassClient.incrementedNonce(nonce);
|
||||
|
||||
return protocolClient.handleResponse(response, incrementedNonce, tab);
|
||||
};
|
||||
|
||||
protocolClient.buildRequest = function(encryptedMessage, nonce, clientID, triggerUnlock = false) {
|
||||
const request = {
|
||||
message: encryptedMessage,
|
||||
nonce: nonce,
|
||||
clientID: clientID,
|
||||
requestID: keepassClient.getRequestId()
|
||||
};
|
||||
|
||||
if (triggerUnlock) {
|
||||
request.triggerUnlock = true;
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
protocolClient.handleResponse = function(response, incrementedNonce, tab) {
|
||||
if (response.message && protocolClient.verifyNonce(response, incrementedNonce)) {
|
||||
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);
|
||||
return parsed;
|
||||
} else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
protocolClient.verifyNonce = function(response, nonce) {
|
||||
if (!response.nonce) {
|
||||
logError('No nonce in reponse');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keepassClient.checkNonceLength(response.nonce)) {
|
||||
logError('Incorrect nonce length');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('Nonce compare failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocolClient.getNonce = function() {
|
||||
return nacl.util.encodeBase64(nacl.randomBytes(protocolClient.keySize));
|
||||
};
|
||||
|
||||
// Creates a random 8 character string for Request ID
|
||||
protocolClient.getRequestId = function() {
|
||||
return Math.random().toString(16).substring(2, 10);
|
||||
};
|
||||
|
||||
protocolClient.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);
|
||||
};
|
||||
|
||||
protocolClient.getNonces = function() {
|
||||
const nonce = protocolClient.getNonce();
|
||||
const incrementedNonce = protocolClient.incrementedNonce(nonce);
|
||||
return [ nonce, incrementedNonce ];
|
||||
};
|
||||
|
||||
protocolClient.checkNonceLength = function(nonce) {
|
||||
return nacl.util.decodeBase64(nonce).length === nacl.secretbox.nonceLength;
|
||||
};
|
||||
|
||||
protocolClient.generateNewKeyPair = function() {
|
||||
keepass.keyPair = nacl.box.keyPair();
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Encrypt/Decrypt
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocolClient.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 '';
|
||||
};
|
||||
|
||||
protocolClient.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
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocolClient.connectToNative = function() {
|
||||
if (protocolClient.nativePort) {
|
||||
protocolClient.nativePort.disconnect();
|
||||
}
|
||||
protocolClient.nativeConnect();
|
||||
};
|
||||
|
||||
protocolClient.nativeConnect = function() {
|
||||
console.log(`${EXTENSION_NAME}: Connecting to native messaging host ${protocolClient.nativeHostName}`);
|
||||
protocolClient.nativePort = browser.runtime.connectNative(protocolClient.nativeHostName);
|
||||
protocolClient.nativePort.onMessage.addListener(protocolClient.onNativeMessage);
|
||||
protocolClient.nativePort.onDisconnect.addListener(onDisconnected);
|
||||
keepass.isConnected = true;
|
||||
return protocolClient.nativePort;
|
||||
};
|
||||
|
||||
function onDisconnected() {
|
||||
protocolClient.nativePort = null;
|
||||
keepass.isConnected = false;
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
keepass.databaseHash = '';
|
||||
|
||||
page.clearAllLogins();
|
||||
keepass.updatePopup('cross');
|
||||
keepass.updateDatabaseHashToContent();
|
||||
logError(`Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)}`);
|
||||
}
|
||||
|
||||
protocolClient.onNativeMessage = function(response) {
|
||||
// Handle database lock/unlock status
|
||||
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
|
||||
keepass.updateDatabase();
|
||||
}
|
||||
};
|
||||
|
|
@ -140,7 +140,7 @@ kpxcBanner.create = async function(credentials = {}) {
|
|||
kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
||||
const saveToDefaultGroup = async function(creds) {
|
||||
const args = [ creds.username, creds.password, creds.url ];
|
||||
const res = await sendMessage('add_credentials', args);
|
||||
const res = await sendMessage('create_credentials', args);
|
||||
kpxcBanner.verifyResult(res);
|
||||
};
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
|||
// Create a new group
|
||||
const newGroup = await sendMessage('create_new_group', [ result.defaultGroup ]);
|
||||
if (newGroup.name && newGroup.uuid) {
|
||||
const res = await sendMessage('add_credentials', [ credentials.username, credentials.password, credentials.url, newGroup.name, newGroup.uuid ]);
|
||||
const res = await sendMessage('create_credentials', [ credentials.username, credentials.password, credentials.url, newGroup.name, newGroup.uuid ]);
|
||||
kpxcBanner.verifyResult(res);
|
||||
} else {
|
||||
kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup'));
|
||||
|
|
@ -180,7 +180,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const res = await sendMessage('add_credentials', [ credentials.username, credentials.password, credentials.url, gname, guuid ]);
|
||||
const res = await sendMessage('create_credentials', [ credentials.username, credentials.password, credentials.url, gname, guuid ]);
|
||||
kpxcBanner.verifyResult(res);
|
||||
return;
|
||||
}
|
||||
|
|
@ -212,7 +212,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
const res = await sendMessage('add_credentials', [ credentials.username, credentials.password, credentials.url, group, groupUuid ]);
|
||||
const res = await sendMessage('create_credentials', [ credentials.username, credentials.password, credentials.url, group, groupUuid ]);
|
||||
kpxcBanner.verifyResult(res);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -102,12 +102,23 @@ kpxc.detectDatabaseChange = async function(response) {
|
|||
kpxc.clearAllFromPage();
|
||||
kpxcIcons.switchIcons();
|
||||
|
||||
// TODO: This doesn't work well anymore.
|
||||
if (document.visibilityState !== 'hidden') {
|
||||
if (response.hash.new !== '') {
|
||||
_called.retrieveCredentials = false;
|
||||
|
||||
// Why is this needed? For the Connection Keys?
|
||||
const settings = await sendMessage('load_settings');
|
||||
kpxc.settings = settings;
|
||||
kpxc.databaseState = DatabaseState.UNLOCKED;
|
||||
|
||||
// TODO: Cleanup this..
|
||||
if (response.associateResult) {
|
||||
if (!response.associateResult.areAllLocked) {
|
||||
kpxc.databaseState = DatabaseState.UNLOCKED;
|
||||
}
|
||||
} else {
|
||||
kpxc.databaseState = DatabaseState.UNLOCKED; // This is important to set correctly!
|
||||
}
|
||||
|
||||
await kpxc.initCredentialFields();
|
||||
kpxcIcons.switchIcons();
|
||||
|
|
@ -115,7 +126,7 @@ kpxc.detectDatabaseChange = async function(response) {
|
|||
// If user has requested a manual fill through context menu the actual credential filling
|
||||
// is handled here when the opened database has been regognized. It's not a pretty hack.
|
||||
const manualFill = await sendMessage('page_get_manual_fill');
|
||||
if (manualFill !== ManualFill.NONE && kpxc.combinations.length > 0) {
|
||||
if (manualFill !== ManualFill.NONE) {
|
||||
await kpxcFill.fillInFromActiveElement(manualFill === ManualFill.PASSWORD);
|
||||
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ const iconClicked = async function(field, icon) {
|
|||
if (kpxc.databaseState !== DatabaseState.UNLOCKED) {
|
||||
// Triggers database unlock
|
||||
await sendMessage('page_set_manual_fill', ManualFill.BOTH);
|
||||
// TODO: Replace with open-database?
|
||||
// TODO: Replace with open-database or get-database-statuses? With legacyProtocol keepass.getDatabaseHash must be used.
|
||||
await sendMessage('get_database_hash', [ false, true ]); // Set triggerUnlock to true
|
||||
field.focus();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,14 +37,16 @@
|
|||
"common/sites.js",
|
||||
"background/nacl.min.js",
|
||||
"background/nacl-util.min.js",
|
||||
"background/client.js",
|
||||
"background/keepass.js",
|
||||
"background/httpauth.js",
|
||||
"background/browserAction.js",
|
||||
"background/page.js",
|
||||
"background/event.js",
|
||||
"background/init.js",
|
||||
"background/protocol.js"
|
||||
"background/protocol.js",
|
||||
"background/protocolClient.js",
|
||||
"background/legacyProtocol.js",
|
||||
"background/legacyProtocolClient.js"
|
||||
]
|
||||
},
|
||||
"content_scripts": [
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ HTMLElement.prototype.hide = function() {
|
|||
this.style.display = 'none';
|
||||
};
|
||||
|
||||
function statusResponse(r) {
|
||||
function statusResponse(status) {
|
||||
$('#initial-state').hide();
|
||||
$('#error-encountered').hide();
|
||||
$('#need-reconfigure').hide();
|
||||
|
|
@ -20,39 +20,70 @@ function statusResponse(r) {
|
|||
$('#lock-database-button').hide();
|
||||
$('#getting-started-guide').hide();
|
||||
|
||||
if (!r.keePassXCAvailable) {
|
||||
$('#error-message').textContent = r.error;
|
||||
if (!status.keePassXCAvailable) {
|
||||
$('#error-message').textContent = status.error;
|
||||
$('#error-encountered').show();
|
||||
|
||||
if (r.showGettingStartedGuideAlert) {
|
||||
if (status.showGettingStartedGuideAlert) {
|
||||
$('#getting-started-guide').show();
|
||||
}
|
||||
|
||||
if (r.showTroubleshootingGuideAlert && reloadCount >= 2) {
|
||||
if (status.showTroubleshootingGuideAlert && reloadCount >= 2) {
|
||||
$('#troubleshooting-guide').show();
|
||||
} else {
|
||||
$('#troubleshooting-guide').hide();
|
||||
}
|
||||
} else if (r.keePassXCAvailable && r.databaseClosed) {
|
||||
$('#database-error-message').textContent = r.error;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only supported with Protocol V2
|
||||
if (status.databaseAssociationStatuses) {
|
||||
// This can be also shown when isAnyAssociated is true?
|
||||
if (status.databaseAssociationStatuses.associationNeeded) {
|
||||
$('#not-configured').show();
|
||||
}
|
||||
|
||||
if (status.keePassXCAvailable && status.databaseAssociationStatuses.areAllLocked) {
|
||||
$('#database-error-message').textContent = status.error;
|
||||
$('#database-not-opened').show();
|
||||
}
|
||||
|
||||
if (status.databaseAssociationStatuses.isAnyAssociated) {
|
||||
$('#configured-and-associated').show();
|
||||
$('#associated-identifier').textContent = status.identifier;
|
||||
$('#lock-database-button').show();
|
||||
|
||||
if (status.usernameFieldDetected) {
|
||||
$('#username-field-detected').show();
|
||||
}
|
||||
|
||||
reloadCount = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.keePassXCAvailable && status.databaseClosed) {
|
||||
$('#database-error-message').textContent = status.error;
|
||||
$('#database-not-opened').show();
|
||||
} else if (!r.configured) {
|
||||
} else if (!status.configured) {
|
||||
$('#not-configured').show();
|
||||
} else if (r.encryptionKeyUnrecognized) {
|
||||
} else if (status.encryptionKeyUnrecognized) {
|
||||
$('#need-reconfigure').show();
|
||||
$('#need-reconfigure-message').textContent = r.error;
|
||||
} else if (!r.associated) {
|
||||
$('#need-reconfigure-message').textContent = status.error;
|
||||
} else if (!status.associated) {
|
||||
$('#need-reconfigure').show();
|
||||
$('#need-reconfigure-message').textContent = r.error;
|
||||
} else if (r.error) {
|
||||
$('#need-reconfigure-message').textContent = status.error;
|
||||
} else if (status.error !== null) {
|
||||
$('#error-encountered').show();
|
||||
$('#error-message').textContent = r.error;
|
||||
$('#error-message').textContent = status.error;
|
||||
} else {
|
||||
$('#configured-and-associated').show();
|
||||
$('#associated-identifier').textContent = r.identifier;
|
||||
$('#associated-identifier').textContent = status.identifier;
|
||||
$('#lock-database-button').show();
|
||||
|
||||
if (r.usernameFieldDetected) {
|
||||
if (status.usernameFieldDetected) {
|
||||
$('#username-field-detected').show();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue