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
a6cbf580b9
commit
db8cd1b1f0
28 changed files with 2252 additions and 1168 deletions
|
|
@ -88,6 +88,7 @@
|
||||||
"assertSearchField": "readonly",
|
"assertSearchField": "readonly",
|
||||||
"assertSearchForm": "readonly",
|
"assertSearchForm": "readonly",
|
||||||
"assertTOTPField": "readonly",
|
"assertTOTPField": "readonly",
|
||||||
|
"AddCredentials": "readonly",
|
||||||
"AssociatedAction": "readonly",
|
"AssociatedAction": "readonly",
|
||||||
"AuthenticatorAssertionResponse": "readonly",
|
"AuthenticatorAssertionResponse": "readonly",
|
||||||
"AuthenticatorAttestationResponse": "readonly",
|
"AuthenticatorAttestationResponse": "readonly",
|
||||||
|
|
@ -126,6 +127,7 @@
|
||||||
"isFirefox": "readonly",
|
"isFirefox": "readonly",
|
||||||
"keepass": "readonly",
|
"keepass": "readonly",
|
||||||
"keepassClient": "readonly",
|
"keepassClient": "readonly",
|
||||||
|
"keepassProtocol": "readonly",
|
||||||
"kpActions": "readonly",
|
"kpActions": "readonly",
|
||||||
"kpErrors": "readonly",
|
"kpErrors": "readonly",
|
||||||
"kpxc": "readonly",
|
"kpxc": "readonly",
|
||||||
|
|
@ -163,6 +165,8 @@
|
||||||
"nacl": "readonly",
|
"nacl": "readonly",
|
||||||
"ORANGE_BUTTON": "readonly",
|
"ORANGE_BUTTON": "readonly",
|
||||||
"page": "readonly",
|
"page": "readonly",
|
||||||
|
"protocol": "readonly",
|
||||||
|
"protocolClient": "readonly",
|
||||||
"Pixels": "readonly",
|
"Pixels": "readonly",
|
||||||
"PublicKeyCredential": "readonly",
|
"PublicKeyCredential": "readonly",
|
||||||
"PREDEFINED_SITELIST": "readonly",
|
"PREDEFINED_SITELIST": "readonly",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This repository does not use prettier for formatting but if you want to use it for testing then you can comment out the following line:
|
# This repository does not use prettier for formatting but if you want to use it for testing then you can comment out the following line:
|
||||||
*
|
#*
|
||||||
|
|
||||||
# Should never format these files:
|
# Should never format these files:
|
||||||
*.min.js
|
*.min.js
|
||||||
|
|
|
||||||
7
dist/manifest_firefox.json
vendored
7
dist/manifest_firefox.json
vendored
|
|
@ -36,14 +36,17 @@
|
||||||
"common/sites.js",
|
"common/sites.js",
|
||||||
"background/nacl.min.js",
|
"background/nacl.min.js",
|
||||||
"background/nacl-util.min.js",
|
"background/nacl-util.min.js",
|
||||||
"background/client.js",
|
|
||||||
"background/keepass.js",
|
"background/keepass.js",
|
||||||
"background/httpauth.js",
|
"background/httpauth.js",
|
||||||
"background/offscreen.js",
|
"background/offscreen.js",
|
||||||
"background/browserAction.js",
|
"background/browserAction.js",
|
||||||
"background/page.js",
|
"background/page.js",
|
||||||
"background/event.js",
|
"background/event.js",
|
||||||
"background/init.js"
|
"background/init.js",
|
||||||
|
"background/protocol.js",
|
||||||
|
"background/protocolClient.js",
|
||||||
|
"background/legacyProtocol.js",
|
||||||
|
"background/legacyProtocolClient.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,10 @@
|
||||||
"message": "No logins found.",
|
"message": "No logins found.",
|
||||||
"description": "No logins found."
|
"description": "No logins found."
|
||||||
},
|
},
|
||||||
|
"errorActionTimeout": {
|
||||||
|
"message": "Action timeout.",
|
||||||
|
"description": "Action timeout."
|
||||||
|
},
|
||||||
"errorMessagePaswordLengthExceeded": {
|
"errorMessagePaswordLengthExceeded": {
|
||||||
"message": "Filled password is longer than field's allowed max length.",
|
"message": "Filled password is longer than field's allowed max length.",
|
||||||
"description": "Error notification text shown when filled password is longer than defined maxLength of the input field."
|
"description": "Error notification text shown when filled password is longer than defined maxLength of the input field."
|
||||||
|
|
@ -1393,6 +1397,14 @@
|
||||||
"lockDatabase": {
|
"lockDatabase": {
|
||||||
"message": "Lock database",
|
"message": "Lock database",
|
||||||
"description": "Lock database button title text."
|
"description": "Lock database button title text."
|
||||||
|
},
|
||||||
|
"lockAllDatabases": {
|
||||||
|
"message": "Lock all databases",
|
||||||
|
"description": "Lock all databases button title text."
|
||||||
|
},
|
||||||
|
"noAction": {
|
||||||
|
"message": "No action",
|
||||||
|
"description": "No action text."
|
||||||
},
|
},
|
||||||
"welcomeText": {
|
"welcomeText": {
|
||||||
"message": "Welcome to KeePassXC-Browser!",
|
"message": "Welcome to KeePassXC-Browser!",
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,17 @@ try {
|
||||||
'../common/sites.js',
|
'../common/sites.js',
|
||||||
'nacl.min.js',
|
'nacl.min.js',
|
||||||
'nacl-util.min.js',
|
'nacl-util.min.js',
|
||||||
'client.js',
|
|
||||||
'keepass.js',
|
'keepass.js',
|
||||||
'httpauth.js',
|
'httpauth.js',
|
||||||
'offscreen.js',
|
'offscreen.js',
|
||||||
'browserAction.js',
|
'browserAction.js',
|
||||||
'page.js',
|
'page.js',
|
||||||
'event.js',
|
'event.js',
|
||||||
'init.js'
|
'init.js',
|
||||||
|
'protocol.js',
|
||||||
|
'protocolClient.js',
|
||||||
|
'legacyProtocol.js',
|
||||||
|
'legacyProtocolClient.js'
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Cannot import background scripts: ', e);
|
console.log('Cannot import background scripts: ', e);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ browserAction.showDefault = async function(tab) {
|
||||||
|
|
||||||
// Get the current tab if no tab given
|
// Get the current tab if no tab given
|
||||||
tab ??= await getCurrentTab();
|
tab ??= await getCurrentTab();
|
||||||
if (!tab) {
|
if (!tab || tab.id < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,415 +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,
|
|
||||||
NO_GROUPS_FOUND: 16,
|
|
||||||
CANNOT_CREATE_NEW_GROUP: 17,
|
|
||||||
NO_VALID_UUID_PROVIDED: 18,
|
|
||||||
ACCESS_TO_ALL_ENTRIES_DENIED: 19,
|
|
||||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED: 20,
|
|
||||||
PASSKEYS_CREDENTIAL_IS_EXCLUDED: 21,
|
|
||||||
PASSKEYS_REQUEST_CANCELED: 22,
|
|
||||||
PASSKEYS_INVALID_USER_VERIFICATION: 23,
|
|
||||||
PASSKEYS_EMPTY_PUBLIC_KEY: 24,
|
|
||||||
PASSKEYS_INVALID_URL_PROVIDED: 25,
|
|
||||||
PASSKEYS_ORIGIN_NOT_ALLOWED: 26,
|
|
||||||
PASSKEYS_DOMAIN_IS_NOT_VALID: 27,
|
|
||||||
PASSKEYS_DOMAIN_RPID_MISMATCH: 28,
|
|
||||||
PASSKEYS_NO_SUPPORTED_ALGORITHMS: 29,
|
|
||||||
PASSKEYS_WAIT_FOR_LIFETIMER: 30,
|
|
||||||
PASSKEYS_UNKNOWN_ERROR: 31,
|
|
||||||
PASSKEYS_INVALID_CHALLENGE: 32,
|
|
||||||
PASSKEYS_INVALID_USER_ID: 33,
|
|
||||||
|
|
||||||
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') },
|
|
||||||
16: { msg: tr('errorMessageNoGroupsFound') },
|
|
||||||
17: { msg: tr('errorMessageCannotCreateNewGroup') },
|
|
||||||
18: { msg: tr('errorMessageNoValidUuidProvided') },
|
|
||||||
19: { msg: tr('errorMessageAccessToAllEntriesDenied') },
|
|
||||||
20: { msg: tr('errorMessagePasskeysAttestationNotSupported') },
|
|
||||||
21: { msg: tr('errorMessagePasskeysCredentialIsExcluded') },
|
|
||||||
22: { msg: tr('errorMessagePasskeysRequestCanceled') },
|
|
||||||
23: { msg: tr('errorMessagePasskeysInvalidUserVerification') },
|
|
||||||
24: { msg: tr('errorMessagePasskeysEmptyPublicKey') },
|
|
||||||
25: { msg: tr('errorMessagePasskeysInvalidUrlProvided') },
|
|
||||||
26: { msg: tr('errorMessagePasskeysOriginNotAllowed') },
|
|
||||||
27: { msg: tr('errorMessagePasskeysDomainNotValid') },
|
|
||||||
28: { msg: tr('errorMessagePasskeysDomainRpIdMismatch') },
|
|
||||||
29: { msg: tr('errorMessagePasskeysNoSupportedAlgorithms') },
|
|
||||||
30: { msg: tr('errorMessagePasskeysWaitforLifeTimer') },
|
|
||||||
31: { msg: tr('errorMessagePasskeysUnknownError') },
|
|
||||||
32: { msg: tr('errorMessagePasskeysInvalidChallenge') },
|
|
||||||
33: { msg: tr('errorMessagePasskeysInvalidUserId') },
|
|
||||||
},
|
|
||||||
|
|
||||||
getError(errorCode) {
|
|
||||||
return this.errorMessages[errorCode].msg;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messageBuffer = {
|
|
||||||
buffer: [],
|
|
||||||
|
|
||||||
addMessage(message) {
|
|
||||||
this.buffer.push(message);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns corresponding message from the response. If the response is an error,
|
|
||||||
// return the first matching action from the buffer.
|
|
||||||
getMessage(response) {
|
|
||||||
const isError = Boolean(!response.nonce && response.error && response.errorCode);
|
|
||||||
return this.buffer.find(message => {
|
|
||||||
if (keepassClient.incrementedNonce(message.request.nonce) === response.nonce
|
|
||||||
|| (isError && message.request?.action === response?.action)) {
|
|
||||||
// Cancel timeout
|
|
||||||
if (message.enableTimeout) {
|
|
||||||
message.cancelTimeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeMessage(message) {
|
|
||||||
const index = this.buffer.indexOf(message);
|
|
||||||
if (index >= 0 && index < this.buffer.length) {
|
|
||||||
this.buffer.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Basic class for a message to be sent. The Promise inside the class will be resolved when
|
|
||||||
// the response to the message is received.
|
|
||||||
class Message {
|
|
||||||
constructor(request, enableTimeout, timeoutValue) {
|
|
||||||
this.enableTimeout = enableTimeout;
|
|
||||||
this.request = request;
|
|
||||||
this.timeout = undefined;
|
|
||||||
|
|
||||||
this.promise = new Promise((resolve, reject) => {
|
|
||||||
this.reject = reject;
|
|
||||||
this.resolve = resolve;
|
|
||||||
|
|
||||||
const messageTimeout = timeoutValue || keepassClient.messageTimeout;
|
|
||||||
|
|
||||||
// Handle timeout
|
|
||||||
if (this.enableTimeout) {
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
const errorMessage = {
|
|
||||||
action: request.action,
|
|
||||||
error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED),
|
|
||||||
errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED
|
|
||||||
};
|
|
||||||
|
|
||||||
keepass.isKeePassXCAvailable = false;
|
|
||||||
resolve(errorMessage);
|
|
||||||
}, messageTimeout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelTimeout() {
|
|
||||||
this.enableTimeout = false;
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
// Messaging
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
|
|
||||||
keepassClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue) {
|
|
||||||
if (!keepassClient.nativePort) {
|
|
||||||
logError('No native messaging port defined.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = new Message(request, enableTimeout, timeoutValue);
|
|
||||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
|
||||||
messageBuffer.addMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
keepassClient.nativePort.postMessage(request);
|
|
||||||
|
|
||||||
const response = await message.promise;
|
|
||||||
|
|
||||||
// Remove a timeouted message
|
|
||||||
if (response.error && response?.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
|
||||||
messageBuffer.removeMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
keepassClient.handleNativeMessage = async function(response) {
|
|
||||||
// Parse through the message buffer to find the corresponding Promise.
|
|
||||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
|
||||||
const message = messageBuffer.getMessage(response);
|
|
||||||
if (message) {
|
|
||||||
message.resolve(response);
|
|
||||||
messageBuffer.removeMessage(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLogMessage('Corresponding request not found in the message buffer for response: ', response);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
// 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();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic response handling
|
|
||||||
keepassClient.handleNativeMessage(response);
|
|
||||||
};
|
|
||||||
|
|
@ -30,34 +30,40 @@ kpxcEvent.showStatus = async function(tab, configured, internalPoll) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
associated: keepass.isAssociated(),
|
associated: keepass.isAssociated(),
|
||||||
|
|
||||||
configured: configured,
|
configured: configured,
|
||||||
databaseClosed: keepass.isDatabaseClosed,
|
databaseClosed: keepass.isDatabaseClosed,
|
||||||
|
databaseAssociationStatuses: keepass.databaseAssociationStatuses,
|
||||||
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
|
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
iframeDetected: iframeDetected,
|
iframeDetected: iframeDetected,
|
||||||
identifier: keyId,
|
identifier: keyId,
|
||||||
keePassXCAvailable: keepass.isKeePassXCAvailable,
|
keePassXCAvailable: keepass.isKeePassXCAvailable,
|
||||||
|
protocolV2: keepass.protocolV2,
|
||||||
showGettingStartedGuideAlert: page.settings.showGettingStartedGuideAlert,
|
showGettingStartedGuideAlert: page.settings.showGettingStartedGuideAlert,
|
||||||
showTroubleshootingGuideAlert: page.settings.showTroubleshootingGuideAlert,
|
showTroubleshootingGuideAlert: page.settings.showTroubleshootingGuideAlert,
|
||||||
usernameFieldDetected: usernameFieldDetected
|
usernameFieldDetected: usernameFieldDetected
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onLoadSettings = async function() {
|
kpxcEvent.loadSettings = async function() {
|
||||||
return await page.initSettings().catch((err) => {
|
return await page.initSettings().catch((err) => {
|
||||||
logError('onLoadSettings error: ' + err);
|
logError('loadSettings error: ' + err);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onLoadKeyRing = async function() {
|
kpxcEvent.isProtocolV2 = async function(tab) {
|
||||||
|
return keepass.protocolV2;
|
||||||
|
};
|
||||||
|
|
||||||
|
kpxcEvent.loadKeyRing = async function() {
|
||||||
const item = await browser.storage.local.get({ 'keyRing': {} }).catch((err) => {
|
const item = await browser.storage.local.get({ 'keyRing': {} }).catch((err) => {
|
||||||
logError('kpxcEvent.onLoadKeyRing error: ' + err);
|
logError('kpxcEvent.loadKeyRing error: ' + err);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
});
|
});
|
||||||
|
|
||||||
keepass.keyRing = item.keyRing;
|
keepass.keyRing = item.keyRing;
|
||||||
|
// TODO: What to do here?
|
||||||
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
|
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
|
||||||
keepass.associated = {
|
keepass.associated = {
|
||||||
value: false,
|
value: false,
|
||||||
|
|
@ -68,23 +74,33 @@ kpxcEvent.onLoadKeyRing = async function() {
|
||||||
return item.keyRing;
|
return item.keyRing;
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onSaveSettings = async function(tab, settings) {
|
kpxcEvent.saveSettings = async function(tab, settings) {
|
||||||
browser.storage.local.set({ 'settings': settings });
|
browser.storage.local.set({ 'settings': settings });
|
||||||
kpxcEvent.onLoadSettings(tab);
|
kpxcEvent.loadSettings(tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onGetStatus = async function(tab, args = []) {
|
kpxcEvent.getStatus = async function(tab, args = []) {
|
||||||
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
|
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
|
||||||
try {
|
try {
|
||||||
const [ internalPoll = false, triggerUnlock = false ] = args;
|
const [ internalPoll = false, triggerUnlock = false ] = args;
|
||||||
|
let configured = false;
|
||||||
|
|
||||||
|
if (keepass.protocolV2) {
|
||||||
|
configured = internalPoll
|
||||||
|
? keepass.databaseAssociationStatuses?.isAnyAssociated
|
||||||
|
: await protocol.testAssociationFromDatabaseStatuses(tab, [ true, triggerUnlock ])?.isAnyAssociated;
|
||||||
|
return kpxcEvent.showStatus(tab, configured, internalPoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol V1
|
||||||
if (!internalPoll) {
|
if (!internalPoll) {
|
||||||
const response = await keepass.testAssociation(tab, [ true, triggerUnlock ]);
|
const response = await keepassProtocol.testAssociation(tab, [ true, triggerUnlock ]);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return kpxcEvent.showStatus(tab, false);
|
return kpxcEvent.showStatus(tab, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const configured = await keepass.isConfigured();
|
configured = await keepass.isConfigured();
|
||||||
return kpxcEvent.showStatus(tab, configured, internalPoll);
|
return kpxcEvent.showStatus(tab, configured, internalPoll);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError('No status shown: ' + err);
|
logError('No status shown: ' + err);
|
||||||
|
|
@ -92,7 +108,7 @@ kpxcEvent.onGetStatus = async function(tab, args = []) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onReconnect = async function(tab) {
|
kpxcEvent.reconnect = async function(tab) {
|
||||||
const configured = await keepass.reconnect(tab);
|
const configured = await keepass.reconnect(tab);
|
||||||
if (configured) {
|
if (configured) {
|
||||||
browser.tabs.sendMessage(tab?.id, {
|
browser.tabs.sendMessage(tab?.id, {
|
||||||
|
|
@ -116,19 +132,19 @@ kpxcEvent.lockDatabase = async function(tab) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onGetTabInformation = async function(tab) {
|
kpxcEvent.getTabInformation = async function(tab) {
|
||||||
const id = tab?.id || page.currentTabId;
|
const id = tab?.id || page.currentTabId;
|
||||||
return page.tabs[id];
|
return page.tabs[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onGetConnectedDatabase = async function() {
|
kpxcEvent.getConnectedDatabase = async function() {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
count: Object.keys(keepass.keyRing).length,
|
count: Object.keys(keepass.keyRing).length,
|
||||||
identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null
|
identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onGetKeePassXCVersions = async function(tab) {
|
kpxcEvent.getKeePassXCVersions = async function(tab) {
|
||||||
if (keepass.currentKeePassXC === '') {
|
if (keepass.currentKeePassXC === '') {
|
||||||
await keepass.getDatabaseHash(tab);
|
await keepass.getDatabaseHash(tab);
|
||||||
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
||||||
|
|
@ -137,22 +153,22 @@ kpxcEvent.onGetKeePassXCVersions = async function(tab) {
|
||||||
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onCheckUpdateKeePassXC = async function() {
|
kpxcEvent.checkUpdateKeePassXC = async function() {
|
||||||
await keepass.checkForNewKeePassXCVersion();
|
await keepass.checkForNewKeePassXCVersion();
|
||||||
return { current: keepass.currentKeePassXC, latest: keepass.latestKeePassXC.version };
|
return { current: keepass.currentKeePassXC, latest: keepass.latestKeePassXC.version };
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onUpdateAvailableKeePassXC = async function() {
|
kpxcEvent.updateAvailableKeePassXC = async function() {
|
||||||
return (Number(page.settings.checkUpdateKeePassXC) !== CHECK_UPDATE_NEVER) ? await keepass.keePassXCUpdateAvailable() : false;
|
return (Number(page.settings.checkUpdateKeePassXC) !== CHECK_UPDATE_NEVER) ? await keepass.keePassXCUpdateAvailable() : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onRemoveCredentialsFromTabInformation = async function(tab) {
|
kpxcEvent.removeCredentialsFromTabInformation = async function(tab) {
|
||||||
const id = tab?.id || page.currentTabId;
|
const id = tab?.id || page.currentTabId;
|
||||||
page.clearCredentials(id);
|
page.clearCredentials(id);
|
||||||
page.clearSubmittedCredentials();
|
page.clearSubmittedCredentials();
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onLoginPopup = async function(tab, logins) {
|
kpxcEvent.initLoginPopup = async function(tab, logins) {
|
||||||
const popupData = {
|
const popupData = {
|
||||||
iconType: 'normal',
|
iconType: 'normal',
|
||||||
popup: 'popup_login'
|
popup: 'popup_login'
|
||||||
|
|
@ -168,7 +184,7 @@ kpxcEvent.initHttpAuth = async function() {
|
||||||
httpAuth.init();
|
httpAuth.init();
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onHTTPAuthPopup = async function(tab, data) {
|
kpxcEvent.initHttpAuthPopup = async function(tab, data) {
|
||||||
const popupData = {
|
const popupData = {
|
||||||
iconType: 'normal',
|
iconType: 'normal',
|
||||||
popup: 'popup_httpauth'
|
popup: 'popup_httpauth'
|
||||||
|
|
@ -178,7 +194,7 @@ kpxcEvent.onHTTPAuthPopup = async function(tab, data) {
|
||||||
await browserAction.show(tab, popupData);
|
await browserAction.show(tab, popupData);
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.onUsernameFieldDetected = async function(tab, detected) {
|
kpxcEvent.usernameFieldDetected = async function(tab, detected) {
|
||||||
if (tab?.id) {
|
if (tab?.id) {
|
||||||
page.tabs[tab.id].usernameFieldDetected = detected;
|
page.tabs[tab.id].usernameFieldDetected = detected;
|
||||||
}
|
}
|
||||||
|
|
@ -221,17 +237,17 @@ kpxcEvent.getIsKeePassXCAvailable = async function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.hideGettingStartedGuideAlert = async function(tab) {
|
kpxcEvent.hideGettingStartedGuideAlert = async function(tab) {
|
||||||
const settings = await kpxcEvent.onLoadSettings();
|
const settings = await kpxcEvent.loadSettings();
|
||||||
settings.showGettingStartedGuideAlert = false;
|
settings.showGettingStartedGuideAlert = false;
|
||||||
|
|
||||||
await kpxcEvent.onSaveSettings(tab, settings);
|
await kpxcEvent.saveSettings(tab, settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcEvent.hideTroubleshootingGuideAlert = async function(tab) {
|
kpxcEvent.hideTroubleshootingGuideAlert = async function(tab) {
|
||||||
const settings = await kpxcEvent.onLoadSettings();
|
const settings = await kpxcEvent.loadSettings();
|
||||||
settings.showTroubleshootingGuideAlert = false;
|
settings.showTroubleshootingGuideAlert = false;
|
||||||
|
|
||||||
await kpxcEvent.onSaveSettings(tab, settings);
|
await kpxcEvent.saveSettings(tab, settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bounce message back to all frames
|
// Bounce message back to all frames
|
||||||
|
|
@ -243,13 +259,13 @@ kpxcEvent.sendBackToTabs = async function(tab, args = []) {
|
||||||
|
|
||||||
// All methods named in this object have to be declared BEFORE this!
|
// All methods named in this object have to be declared BEFORE this!
|
||||||
kpxcEvent.messageHandlers = {
|
kpxcEvent.messageHandlers = {
|
||||||
'add_credentials': keepass.addCredentials,
|
|
||||||
'associate': keepass.associate,
|
'associate': keepass.associate,
|
||||||
'banner_get_position': page.getBannerPosition,
|
'banner_get_position': page.getBannerPosition,
|
||||||
'banner_set_position': page.setBannerPosition,
|
'banner_set_position': page.setBannerPosition,
|
||||||
'check_database_hash': keepass.checkDatabaseHash,
|
'check_database_hash': keepass.checkDatabaseHash,
|
||||||
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
|
'check_update_keepassxc': kpxcEvent.checkUpdateKeePassXC,
|
||||||
'compare_versions': kpxcEvent.compareMultipleVersions,
|
'compare_versions': kpxcEvent.compareMultipleVersions,
|
||||||
|
'create_credentials': keepass.createCredentials,
|
||||||
'create_new_group': keepass.createNewGroup,
|
'create_new_group': keepass.createNewGroup,
|
||||||
'enable_automatic_reconnect': keepass.enableAutomaticReconnect,
|
'enable_automatic_reconnect': keepass.enableAutomaticReconnect,
|
||||||
'disable_automatic_reconnect': keepass.disableAutomaticReconnect,
|
'disable_automatic_reconnect': keepass.disableAutomaticReconnect,
|
||||||
|
|
@ -257,14 +273,14 @@ kpxcEvent.messageHandlers = {
|
||||||
'frame_message': kpxcEvent.sendBackToTabs,
|
'frame_message': kpxcEvent.sendBackToTabs,
|
||||||
'generate_password': keepass.generatePassword,
|
'generate_password': keepass.generatePassword,
|
||||||
'get_color_theme': kpxcEvent.getColorTheme,
|
'get_color_theme': kpxcEvent.getColorTheme,
|
||||||
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
|
'get_connected_database': kpxcEvent.getConnectedDatabase,
|
||||||
'get_database_hash': keepass.getDatabaseHash,
|
'get_database_hash': keepass.getDatabaseHash, // TODO ?
|
||||||
'get_database_groups': keepass.getDatabaseGroups,
|
'get_database_groups': keepass.getDatabaseGroups,
|
||||||
'get_error_message': keepass.getErrorMessage,
|
'get_error_message': keepass.getErrorMessage,
|
||||||
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
|
'get_keepassxc_versions': kpxcEvent.getKeePassXCVersions,
|
||||||
'get_login_list': page.getLoginList,
|
'get_login_list': page.getLoginList,
|
||||||
'get_status': kpxcEvent.onGetStatus,
|
'get_status': kpxcEvent.getStatus,
|
||||||
'get_tab_information': kpxcEvent.onGetTabInformation,
|
'get_tab_information': kpxcEvent.getTabInformation,
|
||||||
'get_totp': keepass.getTotp,
|
'get_totp': keepass.getTotp,
|
||||||
'hide_getting_started_guide_alert': kpxcEvent.hideGettingStartedGuideAlert,
|
'hide_getting_started_guide_alert': kpxcEvent.hideGettingStartedGuideAlert,
|
||||||
'hide_troubleshooting_guide_alert': kpxcEvent.hideTroubleshootingGuideAlert,
|
'hide_troubleshooting_guide_alert': kpxcEvent.hideTroubleshootingGuideAlert,
|
||||||
|
|
@ -272,12 +288,15 @@ kpxcEvent.messageHandlers = {
|
||||||
'init_http_auth': kpxcEvent.initHttpAuth,
|
'init_http_auth': kpxcEvent.initHttpAuth,
|
||||||
'is_connected': kpxcEvent.getIsKeePassXCAvailable,
|
'is_connected': kpxcEvent.getIsKeePassXCAvailable,
|
||||||
'is_iframe_allowed': page.isIframeAllowed,
|
'is_iframe_allowed': page.isIframeAllowed,
|
||||||
|
'is_protocol_v2': kpxcEvent.isProtocolV2,
|
||||||
'is_site_ignored': page.isSiteIgnored,
|
'is_site_ignored': page.isSiteIgnored,
|
||||||
'load_keyring': kpxcEvent.onLoadKeyRing,
|
'load_keyring': kpxcEvent.loadKeyRing,
|
||||||
'load_settings': kpxcEvent.onLoadSettings,
|
'load_settings': kpxcEvent.loadSettings,
|
||||||
'lock_database': kpxcEvent.lockDatabase,
|
'lock_database': kpxcEvent.lockDatabase,
|
||||||
|
'page_clear_auto_lock_requested': page.clearAutoLockRequested,
|
||||||
'page_clear_logins': kpxcEvent.pageClearLogins,
|
'page_clear_logins': kpxcEvent.pageClearLogins,
|
||||||
'page_clear_submitted': page.clearSubmittedCredentials,
|
'page_clear_submitted': page.clearSubmittedCredentials,
|
||||||
|
'page_get_auto_lock_requested': page.getAutoLockRequested,
|
||||||
'page_get_autosubmit_performed': page.getAutoSubmitPerformed,
|
'page_get_autosubmit_performed': page.getAutoSubmitPerformed,
|
||||||
'page_get_login_id': page.getLoginId,
|
'page_get_login_id': page.getLoginId,
|
||||||
'page_get_manual_fill': page.getManualFill,
|
'page_get_manual_fill': page.getManualFill,
|
||||||
|
|
@ -292,16 +311,16 @@ kpxcEvent.messageHandlers = {
|
||||||
'passkeys_register': keepass.passkeysRegister,
|
'passkeys_register': keepass.passkeysRegister,
|
||||||
'password_get_filled': kpxcEvent.passwordGetFilled,
|
'password_get_filled': kpxcEvent.passwordGetFilled,
|
||||||
'password_set_filled': kpxcEvent.passwordSetFilled,
|
'password_set_filled': kpxcEvent.passwordSetFilled,
|
||||||
'popup_login': kpxcEvent.onLoginPopup,
|
'popup_login': kpxcEvent.initLoginPopup,
|
||||||
'reconnect': kpxcEvent.onReconnect,
|
'reconnect': kpxcEvent.reconnect,
|
||||||
'remove_credentials_from_tab_information': kpxcEvent.onRemoveCredentialsFromTabInformation,
|
'remove_credentials_from_tab_information': kpxcEvent.removeCredentialsFromTabInformation,
|
||||||
'request_autotype': keepass.requestAutotype,
|
'request_autotype': keepass.requestAutotype,
|
||||||
'retrieve_credentials': page.retrieveCredentials,
|
'retrieve_credentials': page.retrieveCredentials,
|
||||||
'show_default_browseraction': browserAction.showDefault,
|
'show_default_browseraction': browserAction.showDefault,
|
||||||
'update_credentials': keepass.updateCredentials,
|
'update_credentials': keepass.updateCredentials,
|
||||||
'username_field_detected': kpxcEvent.onUsernameFieldDetected,
|
'username_field_detected': kpxcEvent.usernameFieldDetected,
|
||||||
'save_settings': kpxcEvent.onSaveSettings,
|
'save_settings': kpxcEvent.saveSettings,
|
||||||
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC,
|
'update_available_keepassxc': kpxcEvent.updateAvailableKeePassXC,
|
||||||
'update_context_menu': page.updateContextMenu,
|
'update_context_menu': page.updateContextMenu,
|
||||||
'update_popup': page.updatePopup
|
'update_popup': page.updatePopup
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,9 @@ httpAuth.handleRequestCallback = function(details, callback) {
|
||||||
httpAuth.processPendingCallbacks(details, callback, callback);
|
httpAuth.processPendingCallbacks(details, callback, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
httpAuth.retrieveCredentials = async function(tabId, url, submitUrl) {
|
httpAuth.getCredentials = async function(tabId, url, submitUrl) {
|
||||||
return await keepass.retrieveCredentials(tabId, [ url, submitUrl, false, true ]).catch((err) => {
|
return await keepass.getCredentials(tabId, [ url, submitUrl, false, true ]).catch((err) => {
|
||||||
logError('httpAuth.retrieveCredentials error: ' + err);
|
logError('httpAuth.getCredentials error: ' + err);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -72,7 +72,7 @@ httpAuth.processPendingCallbacks = async function(details, resolve, reject) {
|
||||||
|
|
||||||
details.searchUrl = (details.isProxy && details.proxyUrl) ? details.proxyUrl : details.url;
|
details.searchUrl = (details.isProxy && details.proxyUrl) ? details.proxyUrl : details.url;
|
||||||
|
|
||||||
const logins = await httpAuth.retrieveCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl);
|
const logins = await httpAuth.getCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl);
|
||||||
httpAuth.loginOrShowCredentials(logins, details, resolve, reject);
|
httpAuth.loginOrShowCredentials(logins, details, resolve, reject);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -90,7 +90,10 @@ httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) {
|
||||||
if (page.settings.showNotifications) {
|
if (page.settings.showNotifications) {
|
||||||
showNotification(tr('multipleCredentialsDetected'));
|
showNotification(tr('multipleCredentialsDetected'));
|
||||||
}
|
}
|
||||||
kpxcEvent.onHTTPAuthPopup({ 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
|
kpxcEvent.initHttpAuthPopup(
|
||||||
|
{ id: details.tabId },
|
||||||
|
{ logins: logins, url: details.searchUrl, resolve: resolve },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logError('No logins found for HTTP Basic Auth.');
|
logError('No logins found for HTTP Basic Auth.');
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const keepass = {};
|
const keepass = {};
|
||||||
keepass.associated = { 'value': false, 'hash': null };
|
keepass.associated = { value: false, hash: null };
|
||||||
keepass.keyPair = { publicKey: null, secretKey: null };
|
keepass.cacheTimeout = 30 * 1000; // Milliseconds
|
||||||
keepass.serverPublicKey = '';
|
|
||||||
keepass.clientID = '';
|
keepass.clientID = '';
|
||||||
|
keepass.currentKeePassXC = '';
|
||||||
|
keepass.databaseAssociationStatuses = {};
|
||||||
|
keepass.databaseHash = ''; // Hash of the active database
|
||||||
|
keepass.databaseStatuses = [];
|
||||||
keepass.isConnected = false;
|
keepass.isConnected = false;
|
||||||
keepass.isDatabaseClosed = true;
|
keepass.isDatabaseClosed = true;
|
||||||
keepass.isKeePassXCAvailable = false;
|
|
||||||
keepass.isEncryptionKeyUnrecognized = false;
|
keepass.isEncryptionKeyUnrecognized = false;
|
||||||
keepass.currentKeePassXC = '';
|
keepass.isKeePassXCAvailable = false;
|
||||||
keepass.requiredKeePassXC = '2.3.1';
|
keepass.keyPair = { publicKey: null, secretKey: null };
|
||||||
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
|
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
|
||||||
keepass.cacheTimeout = 30 * 1000; // Milliseconds
|
|
||||||
keepass.databaseHash = '';
|
|
||||||
keepass.previousDatabaseHash = '';
|
keepass.previousDatabaseHash = '';
|
||||||
|
keepass.protocolV2 = false;
|
||||||
keepass.reconnectLoop = null;
|
keepass.reconnectLoop = null;
|
||||||
|
keepass.requiredKeePassXC = '2.3.1';
|
||||||
|
keepass.serverPublicKey = '';
|
||||||
|
|
||||||
const kpActions = {
|
const kpActions = {
|
||||||
SET_LOGIN: 'set-login',
|
SET_LOGIN: 'set-login',
|
||||||
|
|
@ -33,7 +36,91 @@ const kpActions = {
|
||||||
GET_TOTP: 'get-totp',
|
GET_TOTP: 'get-totp',
|
||||||
REQUEST_AUTOTYPE: 'request-autotype',
|
REQUEST_AUTOTYPE: 'request-autotype',
|
||||||
PASSKEYS_REGISTER: 'passkeys-register',
|
PASSKEYS_REGISTER: 'passkeys-register',
|
||||||
PASSKEYS_GET: 'passkeys-get'
|
PASSKEYS_GET: 'passkeys-get',
|
||||||
|
// Protocol V2
|
||||||
|
CREATE_CREDENTIALS: 'create-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,
|
||||||
|
NO_GROUPS_FOUND: 16,
|
||||||
|
CANNOT_CREATE_NEW_GROUP: 17,
|
||||||
|
NO_VALID_UUID_PROVIDED: 18,
|
||||||
|
ACCESS_TO_ALL_ENTRIES_DENIED: 19,
|
||||||
|
PASSKEYS_ATTESTATION_NOT_SUPPORTED: 20,
|
||||||
|
PASSKEYS_CREDENTIAL_IS_EXCLUDED: 21,
|
||||||
|
PASSKEYS_REQUEST_CANCELED: 22,
|
||||||
|
PASSKEYS_INVALID_USER_VERIFICATION: 23,
|
||||||
|
PASSKEYS_EMPTY_PUBLIC_KEY: 24,
|
||||||
|
PASSKEYS_INVALID_URL_PROVIDED: 25,
|
||||||
|
PASSKEYS_ORIGIN_NOT_ALLOWED: 26,
|
||||||
|
PASSKEYS_DOMAIN_IS_NOT_VALID: 27,
|
||||||
|
PASSKEYS_DOMAIN_RPID_MISMATCH: 28,
|
||||||
|
PASSKEYS_NO_SUPPORTED_ALGORITHMS: 29,
|
||||||
|
PASSKEYS_WAIT_FOR_LIFETIMER: 30,
|
||||||
|
PASSKEYS_UNKNOWN_ERROR: 31,
|
||||||
|
PASSKEYS_INVALID_CHALLENGE: 32,
|
||||||
|
PASSKEYS_INVALID_USER_ID: 33,
|
||||||
|
ACTION_TIMEOUT: 34,
|
||||||
|
|
||||||
|
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') },
|
||||||
|
16: { msg: tr('errorMessageNoGroupsFound') },
|
||||||
|
17: { msg: tr('errorMessageCannotCreateNewGroup') },
|
||||||
|
18: { msg: tr('errorMessageNoValidUuidProvided') },
|
||||||
|
19: { msg: tr('errorMessageAccessToAllEntriesDenied') },
|
||||||
|
20: { msg: tr('errorMessagePasskeysAttestationNotSupported') },
|
||||||
|
21: { msg: tr('errorMessagePasskeysCredentialIsExcluded') },
|
||||||
|
22: { msg: tr('errorMessagePasskeysRequestCanceled') },
|
||||||
|
23: { msg: tr('errorMessagePasskeysInvalidUserVerification') },
|
||||||
|
24: { msg: tr('errorMessagePasskeysEmptyPublicKey') },
|
||||||
|
25: { msg: tr('errorMessagePasskeysInvalidUrlProvided') },
|
||||||
|
26: { msg: tr('errorMessagePasskeysOriginNotAllowed') },
|
||||||
|
27: { msg: tr('errorMessagePasskeysDomainNotValid') },
|
||||||
|
28: { msg: tr('errorMessagePasskeysDomainRpIdMismatch') },
|
||||||
|
29: { msg: tr('errorMessagePasskeysNoSupportedAlgorithms') },
|
||||||
|
30: { msg: tr('errorMessagePasskeysWaitforLifeTimer') },
|
||||||
|
31: { msg: tr('errorMessagePasskeysUnknownError') },
|
||||||
|
32: { msg: tr('errorMessagePasskeysInvalidChallenge') },
|
||||||
|
33: { msg: tr('errorMessagePasskeysInvalidUserId') },
|
||||||
|
34: { msg: tr('errorActionTimeout') },
|
||||||
|
},
|
||||||
|
|
||||||
|
getError(errorCode) {
|
||||||
|
return this.errorMessages[errorCode].msg;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': null }, 'keyRing': {} }).then((item) => {
|
browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': null }, 'keyRing': {} }).then((item) => {
|
||||||
|
|
@ -42,626 +129,75 @@ browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': n
|
||||||
});
|
});
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Commands
|
// Command wrappers for events
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
keepass.addCredentials = async function(tab, args = []) {
|
keepass.associate = async function(tab, args = []) {
|
||||||
const [ username, password, url, group, groupUuid ] = args;
|
return keepass.protocolV2 ? await protocol.associate(tab, args) : await keepassProtocol.associate(tab, args);
|
||||||
return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
keepass.updateCredentials = async function(tab, args = []) {
|
keepass.createCredentials = async function(tab, args = []) {
|
||||||
try {
|
return keepass.protocolV2
|
||||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
? await protocol.createCredentials(tab, args)
|
||||||
const taResponse = await keepass.testAssociation(tab);
|
: await keepassProtocol.addCredentials(tab, args);
|
||||||
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 kpAction = kpActions.GET_LOGINS;
|
|
||||||
const nonce = keepassClient.getNonce();
|
|
||||||
const [ dbid ] = keepass.getCryptoKey();
|
|
||||||
|
|
||||||
const messageData = {
|
|
||||||
action: kpAction,
|
|
||||||
id: dbid,
|
|
||||||
url: url,
|
|
||||||
keys: keepass.getCryptoKeys()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (submiturl) {
|
|
||||||
messageData.submitUrl = submiturl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpAuth) {
|
|
||||||
messageData.httpAuth = 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
|
||||||
if (response) {
|
|
||||||
entries = removeDuplicateEntries(response.entries);
|
|
||||||
keepass.updateLastUsed(keepass.databaseHash);
|
|
||||||
|
|
||||||
if (entries.length === 0) {
|
|
||||||
// Questionmark-icon is not triggered, so we have to trigger for the normal symbol
|
|
||||||
browserAction.showDefault(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const taResponse = await keepass.testAssociation(tab);
|
|
||||||
if (!taResponse) {
|
|
||||||
browserAction.showDefault(tab);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!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 undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = 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.showDefault(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.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 === '') || response.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
|
||||||
keepass.isKeePassXCAvailable = false;
|
|
||||||
keepass.isConnected = 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.createNewGroup = async function(tab, args = []) {
|
keepass.createNewGroup = async function(tab, args = []) {
|
||||||
try {
|
return keepass.protocolV2
|
||||||
const [ groupName ] = args;
|
? await protocol.createNewGroup(tab, args)
|
||||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
: await keepassProtocol.createNewGroup(tab, args);
|
||||||
if (!taResponse) {
|
};
|
||||||
browserAction.showDefault(tab);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
keepass.clearErrorMessage(tab);
|
keepass.generatePassword = async function(tab, args = []) {
|
||||||
|
return keepass.protocolV2
|
||||||
|
? await protocol.generatePassword(tab, args)
|
||||||
|
: await keepassProtocol.generatePassword(tab, args);
|
||||||
|
};
|
||||||
|
|
||||||
if (!keepass.isConnected) {
|
keepass.getCredentials = async function(tab, args = []) {
|
||||||
return [];
|
return keepass.protocolV2
|
||||||
}
|
? await protocol.getCredentials(tab, args)
|
||||||
|
: await keepassProtocol.retrieveCredentials(tab, args);
|
||||||
|
};
|
||||||
|
|
||||||
const kpAction = kpActions.CREATE_NEW_GROUP;
|
keepass.getDatabaseGroups = async function(tab, args = []) {
|
||||||
const nonce = keepassClient.getNonce();
|
return keepass.protocolV2
|
||||||
|
? await protocol.getDatabaseGroups(tab, args)
|
||||||
|
: await keepassProtocol.getDatabaseGroups(tab, args);
|
||||||
|
};
|
||||||
|
|
||||||
const messageData = {
|
keepass.getDatabaseHash = async function(tab, args = []) {
|
||||||
action: kpAction,
|
return keepass.protocolV2
|
||||||
groupName: groupName
|
? await protocol.getDatabaseStatuses(tab, args)
|
||||||
};
|
: await keepassProtocol.getDatabaseHash(tab, args);
|
||||||
|
|
||||||
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.getTotp = async function(tab, args = []) {
|
keepass.getTotp = async function(tab, args = []) {
|
||||||
const [ uuid, oldTotp ] = args;
|
return keepass.protocolV2 ? await protocol.getTotp(tab, args) : await keepassProtocol.getTotp(tab, args);
|
||||||
if (!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}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
keepass.requestAutotype = async function(tab, args = []) {
|
keepass.lockDatabase = async function(tab, args = []) {
|
||||||
if (!keepass.isConnected) {
|
return keepass.protocolV2 ? await protocol.lockDatabase(tab, args) : await keepassProtocol.lockDatabase(tab, args);
|
||||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const kpAction = kpActions.REQUEST_AUTOTYPE;
|
|
||||||
const nonce = keepassClient.getNonce();
|
|
||||||
const search = await page.getBaseDomainFromUrl(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.passkeysRegister = async function(tab, args = []) {
|
|
||||||
try {
|
|
||||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
|
||||||
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
|
||||||
browserAction.showDefault(tab);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const kpAction = kpActions.PASSKEYS_REGISTER;
|
|
||||||
const nonce = keepassClient.getNonce();
|
|
||||||
const [ publicKey, origin ] = args;
|
|
||||||
|
|
||||||
const messageData = {
|
|
||||||
action: kpAction,
|
|
||||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
|
||||||
origin: origin,
|
|
||||||
groupName: page?.settings?.defaultPasskeyGroup,
|
|
||||||
keys: keepass.getCryptoKeys()
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
|
||||||
if (response) {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
browserAction.showDefault(tab);
|
|
||||||
return [];
|
|
||||||
} catch (err) {
|
|
||||||
logError(`passkeysRegister failed: ${err}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
keepass.passkeysGet = async function(tab, args = []) {
|
keepass.passkeysGet = async function(tab, args = []) {
|
||||||
try {
|
return keepass.protocolV2 ? await protocol.passkeysGet(tab, args) : await keepassProtocol.passkeysGet(tab, args);
|
||||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
};
|
||||||
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
|
||||||
browserAction.showDefault(tab);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const kpAction = kpActions.PASSKEYS_GET;
|
keepass.passkeysRegister = async function(tab, args = []) {
|
||||||
const nonce = keepassClient.getNonce();
|
return keepass.protocolV2 ? await protocol.passkeysGet(tab, args) : await keepassProtocol.passkeysGet(tab, args);
|
||||||
const publicKey = args[0];
|
};
|
||||||
const origin = args[1];
|
|
||||||
|
|
||||||
const messageData = {
|
keepass.requestAutotype = async function (tab, args = []) {
|
||||||
action: kpAction,
|
return keepass.protocolV2
|
||||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
? await protocol.requestAutotype(tab, args)
|
||||||
origin: origin,
|
: await keepassProtocol.requestAutotype(tab, args);
|
||||||
keys: keepass.getCryptoKeys()
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
keepass.updateCredentials = async function (tab, args = []) {
|
||||||
if (response) {
|
return keepass.protocolV2
|
||||||
return response;
|
? await protocol.updateCredentials(tab, args)
|
||||||
}
|
: await keepassProtocol.updateCredentials(tab, args);
|
||||||
|
|
||||||
browserAction.showDefault(tab);
|
|
||||||
return [];
|
|
||||||
} catch (err) {
|
|
||||||
logError(`passkeysGet failed: ${err}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
@ -805,26 +341,55 @@ keepass.disableAutomaticReconnect = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
keepass.reconnect = async function(tab = null, connectionTimeout = 1500) {
|
keepass.reconnect = async function(tab = null, connectionTimeout = 1500) {
|
||||||
keepassClient.connectToNative();
|
protocolClient.connectToNative();
|
||||||
keepass.generateNewKeyPair();
|
protocolClient.generateNewKeyPair();
|
||||||
const keyChangeResult = await keepass.changePublicKeys(tab, !!connectionTimeout, connectionTimeout).catch(() => false);
|
|
||||||
|
const keyChangeResult = await protocol
|
||||||
|
.changePublicKeys(tab, !!connectionTimeout, connectionTimeout)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
// Change public keys timeout
|
// Change public keys timeout
|
||||||
if (!keyChangeResult) {
|
if (!keyChangeResult) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = await keepass.getDatabaseHash(tab);
|
if (!keepass.protocolV2) {
|
||||||
if (hash !== '') {
|
const hash = await keepass.getDatabaseHash(tab);
|
||||||
keepass.clearErrorMessage(tab);
|
if (hash !== '') {
|
||||||
|
keepass.clearErrorMessage(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
await keepassProtocol.testAssociation();
|
||||||
|
await keepass.isConfigured();
|
||||||
}
|
}
|
||||||
|
|
||||||
await keepass.testAssociation();
|
// TODO: What to do with Protocol V2?
|
||||||
await keepass.isConfigured();
|
|
||||||
keepass.updateDatabaseHashToContent();
|
keepass.updateDatabaseHashToContent();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Error handling
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
// Utils
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
@ -863,7 +428,9 @@ keepass.setcurrentKeePassXCVersion = function(version) {
|
||||||
keepass.keePassXCUpdateAvailable = async function() {
|
keepass.keePassXCUpdateAvailable = async function() {
|
||||||
const checkUpdate = Number(page.settings.checkUpdateKeePassXC);
|
const checkUpdate = Number(page.settings.checkUpdateKeePassXC);
|
||||||
if (checkUpdate !== CHECK_UPDATE_NEVER) {
|
if (checkUpdate !== CHECK_UPDATE_NEVER) {
|
||||||
const lastChecked = (keepass.latestKeePassXC.lastChecked) ? new Date(keepass.latestKeePassXC.lastChecked) : new Date(1986, 11, 21);
|
const lastChecked = keepass.latestKeePassXC.lastChecked
|
||||||
|
? new Date(keepass.latestKeePassXC.lastChecked)
|
||||||
|
: new Date(1986, 11, 21);
|
||||||
const daysSinceLastCheck = Math.floor(((new Date()).getTime() - lastChecked.getTime()) / 86400000);
|
const daysSinceLastCheck = Math.floor(((new Date()).getTime() - lastChecked.getTime()) / 86400000);
|
||||||
if (daysSinceLastCheck >= checkUpdate) {
|
if (daysSinceLastCheck >= checkUpdate) {
|
||||||
await keepass.checkForNewKeePassXCVersion();
|
await keepass.checkForNewKeePassXCVersion();
|
||||||
|
|
@ -891,23 +458,6 @@ keepass.checkForNewKeePassXCVersion = async function() {
|
||||||
keepass.latestKeePassXC.lastChecked = new Date().valueOf();
|
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() {
|
keepass.updatePopup = function() {
|
||||||
if (page && page.tabs.length > 0) {
|
if (page && page.tabs.length > 0) {
|
||||||
browserAction.showDefault();
|
browserAction.showDefault();
|
||||||
|
|
@ -915,14 +465,23 @@ keepass.updatePopup = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Updates the database hashes to content script
|
// Updates the database hashes to content script
|
||||||
keepass.updateDatabase = async function() {
|
keepass.updateDatabase = async function(tab) {
|
||||||
keepass.associated.value = false;
|
keepass.associated.value = false;
|
||||||
keepass.associated.hash = null;
|
keepass.associated.hash = null;
|
||||||
page.clearAllLogins();
|
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(tab);
|
||||||
|
keepass.updateDatabaseHashToContent(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
keepass.updatePopup();
|
// Legacy protocol
|
||||||
|
await keepassProtocol.testAssociation(null, [ true ]);
|
||||||
|
keepass.updatePopup(tab);
|
||||||
keepass.updateDatabaseHashToContent();
|
keepass.updateDatabaseHashToContent();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -959,7 +518,7 @@ keepass.compareMultipleVersions = function(versions, current, canBeEqual = true)
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeDuplicateEntries = function(arr) {
|
keepass.removeDuplicateEntries = function(arr) {
|
||||||
const newArray = [];
|
const newArray = [];
|
||||||
|
|
||||||
for (const a of arr) {
|
for (const a of arr) {
|
||||||
|
|
|
||||||
588
keepassxc-browser/background/legacyProtocol.js
Normal file
588
keepassxc-browser/background/legacyProtocol.js
Normal file
|
|
@ -0,0 +1,588 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Legacy protocol for KeePassXC 2.7.x 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 = 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.showDefault(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.generatePassword = async function(tab) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const taResponse = await keepassProtocol.testAssociation(tab);
|
||||||
|
if (!taResponse) {
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!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 undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
protocol.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
|
||||||
|
};
|
||||||
|
|
||||||
|
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 === '') || response.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||||
|
keepass.isKeePassXCAvailable = false;
|
||||||
|
keepass.isConnected = 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 (!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.passkeysGet = async function(tab, args = []) {
|
||||||
|
try {
|
||||||
|
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||||
|
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const kpAction = kpActions.PASSKEYS_GET;
|
||||||
|
const nonce = protocolClient.getNonce();
|
||||||
|
const publicKey = args[0];
|
||||||
|
const origin = args[1];
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpAction,
|
||||||
|
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||||
|
origin: origin,
|
||||||
|
keys: keepass.getCryptoKeys()
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
} catch (err) {
|
||||||
|
logError(`passkeysGet failed: ${err}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
keepassProtocol.passkeysRegister = async function(tab, args = []) {
|
||||||
|
try {
|
||||||
|
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||||
|
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const kpAction = kpActions.PASSKEYS_REGISTER;
|
||||||
|
const nonce = protocolClient.getNonce();
|
||||||
|
const [ publicKey, origin ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpAction,
|
||||||
|
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||||
|
origin: origin,
|
||||||
|
groupName: page?.settings?.defaultPasskeyGroup,
|
||||||
|
keys: keepass.getCryptoKeys()
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
} catch (err) {
|
||||||
|
logError(`passkeysRegister failed: ${err}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = await page.getBaseDomainFromUrl(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 kpAction = kpActions.GET_LOGINS;
|
||||||
|
const nonce = protocolClient.getNonce();
|
||||||
|
const [ dbid ] = keepass.getCryptoKey();
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpAction,
|
||||||
|
id: dbid,
|
||||||
|
url: url,
|
||||||
|
keys: keepass.getCryptoKeys()
|
||||||
|
};
|
||||||
|
|
||||||
|
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.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 ? AddCredentials.UPDATED : AddCredentials.CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return successMessage;
|
||||||
|
} else {
|
||||||
|
return AddCredentials.ERROR;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logError(`updateCredentials failed: ${err}`);
|
||||||
|
return AddCredentials.ERROR;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
232
keepassxc-browser/background/legacyProtocolClient.js
Normal file
232
keepassxc-browser/background/legacyProtocolClient.js
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const messageBuffer = {
|
||||||
|
buffer: [],
|
||||||
|
|
||||||
|
addMessage(message) {
|
||||||
|
this.buffer.push(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Returns corresponding message from the response. If the response is an error,
|
||||||
|
// return the first matching action from the buffer.
|
||||||
|
getMessage(response) {
|
||||||
|
const isError = Boolean(!response.nonce && response.error && response.errorCode);
|
||||||
|
return this.buffer.find(message => {
|
||||||
|
if (protocolClient.incrementedNonce(message.request.nonce) === response.nonce
|
||||||
|
|| (isError && message.request?.action === response?.action)) {
|
||||||
|
// Cancel timeout
|
||||||
|
if (message.enableTimeout) {
|
||||||
|
message.cancelTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMessage(message) {
|
||||||
|
const index = this.buffer.indexOf(message);
|
||||||
|
if (index >= 0 && index < this.buffer.length) {
|
||||||
|
this.buffer.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basic class for a message to be sent. The Promise inside the class will be resolved when
|
||||||
|
// the response to the message is received.
|
||||||
|
class Message {
|
||||||
|
constructor(request, enableTimeout, timeoutValue) {
|
||||||
|
this.enableTimeout = enableTimeout;
|
||||||
|
this.request = request;
|
||||||
|
this.timeout = undefined;
|
||||||
|
|
||||||
|
this.promise = new Promise((resolve, reject) => {
|
||||||
|
this.reject = reject;
|
||||||
|
this.resolve = resolve;
|
||||||
|
|
||||||
|
const messageTimeout = timeoutValue || protocolClient.messageTimeout;
|
||||||
|
|
||||||
|
// Handle timeout
|
||||||
|
if (this.enableTimeout) {
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
const errorMessage = {
|
||||||
|
action: request.action,
|
||||||
|
error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED),
|
||||||
|
errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED
|
||||||
|
};
|
||||||
|
|
||||||
|
keepass.isKeePassXCAvailable = false;
|
||||||
|
resolve(errorMessage);
|
||||||
|
}, messageTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTimeout() {
|
||||||
|
this.enableTimeout = false;
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy client for KeePassXC 2.7.x and older
|
||||||
|
const keepassClient = {};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Messaging
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
keepassClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue) {
|
||||||
|
if (!protocolClient.nativePort) {
|
||||||
|
logError('No native messaging port defined.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = new Message(request, enableTimeout, timeoutValue);
|
||||||
|
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||||
|
messageBuffer.addMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
protocolClient.nativePort.postMessage(request);
|
||||||
|
|
||||||
|
const response = await message.promise;
|
||||||
|
|
||||||
|
// Remove a timeouted message
|
||||||
|
if (response.error && response?.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||||
|
messageBuffer.removeMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
keepassClient.handleNativeMessage = async function(response) {
|
||||||
|
// Parse through the message buffer to find the corresponding Promise.
|
||||||
|
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||||
|
const message = messageBuffer.getMessage(response);
|
||||||
|
if (message) {
|
||||||
|
message.resolve(response);
|
||||||
|
messageBuffer.removeMessage(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLogMessage('Corresponding request not found in the message buffer for response: ', response);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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';
|
||||||
|
};
|
||||||
|
|
@ -43,6 +43,7 @@ const defaultSettings = {
|
||||||
const AUTO_SUBMIT_TIMEOUT = 5000;
|
const AUTO_SUBMIT_TIMEOUT = 5000;
|
||||||
|
|
||||||
const page = {};
|
const page = {};
|
||||||
|
page.autoLockRequested = false;
|
||||||
page.autoSubmitPerformed = false;
|
page.autoSubmitPerformed = false;
|
||||||
page.attributeMenuItems = [];
|
page.attributeMenuItems = [];
|
||||||
page.blockedTabs = [];
|
page.blockedTabs = [];
|
||||||
|
|
@ -214,6 +215,14 @@ page.clearSubmittedCredentials = async function() {
|
||||||
page.submittedCredentials = {};
|
page.submittedCredentials = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
page.clearAutoLockRequested = async function() {
|
||||||
|
page.autoLockRequested = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
page.getAutoLockRequested = async function() {
|
||||||
|
return page.autoLockRequested;
|
||||||
|
};
|
||||||
|
|
||||||
page.createTabEntry = async function(tabId) {
|
page.createTabEntry = async function(tabId) {
|
||||||
page.tabs[tabId] = {
|
page.tabs[tabId] = {
|
||||||
allowIframes: false,
|
allowIframes: false,
|
||||||
|
|
@ -252,8 +261,14 @@ page.retrieveCredentials = async function(tab, args = []) {
|
||||||
page.currentRequest.tabId = tab.id;
|
page.currentRequest.tabId = tab.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
page.tabs[tab.id].credentials = credentials;
|
||||||
|
|
||||||
|
if (credentials.autoLockRequested) {
|
||||||
|
page.autoLockRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
return credentials;
|
return credentials;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
546
keepassxc-browser/background/protocol.js
Normal file
546
keepassxc-browser/background/protocol.js
Normal file
|
|
@ -0,0 +1,546 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Protocol V2
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const protocol = {};
|
||||||
|
|
||||||
|
protocol.associate = async function(tab, args = []) {
|
||||||
|
if (!keepass.isKeePassXCAvailable) {
|
||||||
|
return AssociatedAction.NOT_ASSOCIATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
keepass.clearErrorMessage(tab);
|
||||||
|
|
||||||
|
const publicKey = protocolClient.getPublicConnectionKey();
|
||||||
|
const idKey = protocolClient.generateIdKey();
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.ASSOCIATE,
|
||||||
|
idKey: idKey,
|
||||||
|
publicKey: publicKey
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData, false, true);
|
||||||
|
if (response && response.id && response.hash) {
|
||||||
|
keepass.setCryptoKey(response.id, idKey);
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unencrypted
|
||||||
|
protocol.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 = protocolClient.getPublicConnectionKey();
|
||||||
|
const [ nonce, incrementedNonce ] = protocolClient.getNonces();
|
||||||
|
keepass.clientID = protocolClient.generateClientId();
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
action: kpAction,
|
||||||
|
clientID: keepass.clientID,
|
||||||
|
nonce: nonce,
|
||||||
|
publicKey: key,
|
||||||
|
requestID: protocolClient.getRequestId()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await keepassClient.sendNativeMessage(request, enableTimeout, connectionTimeout);
|
||||||
|
if (response.error && response.errorCode) {
|
||||||
|
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.setcurrentKeePassXCVersion(response.version);
|
||||||
|
keepass.protocolV2 = response?.protocolVersion === 2;
|
||||||
|
|
||||||
|
const verified = keepass.protocolV2
|
||||||
|
? protocolClient.verifyNonce(response, incrementedNonce)
|
||||||
|
: keepassClient.verifyKeyResponse(response, key, incrementedNonce);
|
||||||
|
if (!response?.publicKey || !verified) {
|
||||||
|
if (tab && page.tabs[tab.id]) {
|
||||||
|
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.updateDatabaseHashToContent();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.createCredentials = async function(tab, args = []) {
|
||||||
|
const [ username, password, url, group, groupUuid ] = args;
|
||||||
|
return protocol.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.createNewGroup = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.clearErrorMessage(tab);
|
||||||
|
|
||||||
|
const [ groupName ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.CREATE_NEW_GROUP,
|
||||||
|
groupName: groupName,
|
||||||
|
keys: protocol.getCurrentKey()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: Handle errors
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
keepass.updateLastUsed(keepass.databaseHash);
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
logError('getDatabaseGroups rejected');
|
||||||
|
}
|
||||||
|
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
} catch (err) {
|
||||||
|
logError(`createNewGroup failed: ${err}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.generatePassword = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.GENERATE_PASSWORD,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
if (response.error && response.errorCode) {
|
||||||
|
keepass.handleError(tab, response.errorCode);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = response.entries ?? response.password;
|
||||||
|
keepass.updateLastUsed(keepass.databaseHash);
|
||||||
|
return password;
|
||||||
|
} else {
|
||||||
|
logError('generatePassword rejected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
} catch (err) {
|
||||||
|
logError(`generatePassword failed: ${err}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.getCredentials = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.clearErrorMessage(tab);
|
||||||
|
|
||||||
|
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||||
|
let entries = [];
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.GET_CREDENTIALS,
|
||||||
|
keys: protocol.getKeys(),
|
||||||
|
url: url
|
||||||
|
};
|
||||||
|
|
||||||
|
if (submiturl) {
|
||||||
|
messageData.submitUrl = submiturl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpAuth) {
|
||||||
|
messageData.httpAuth = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData, false, triggerUnlock);
|
||||||
|
if (response) {
|
||||||
|
if (response.error && response.errorCode) {
|
||||||
|
keepass.handleError(tab, response.errorCode);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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(`getCredentials failed: ${err}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.getDatabaseGroups = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.clearErrorMessage(tab);
|
||||||
|
|
||||||
|
let groups = [];
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.GET_DATABASE_GROUPS,
|
||||||
|
keys: protocol.getCurrentKey()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
if (response.error && response.errorCode) {
|
||||||
|
keepass.handleError(tab, response.errorCode);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.getDatabaseStatuses = async function(tab, args = []) {
|
||||||
|
if (!keepass.isKeePassXCAvailable) {
|
||||||
|
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keepass.serverPublicKey) {
|
||||||
|
await protocol.changePublicKeys(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.GET_DATABASE_STATUSES,
|
||||||
|
keys: protocol.getKeys()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.handleError(tab, kpErrors.ACTION_TIMEOUT);
|
||||||
|
} catch (err) {
|
||||||
|
logError(`getDatabaseStatuses failed: ${err}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.getTotp = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.GET_TOTP,
|
||||||
|
keys: protocol.getKeys(),
|
||||||
|
uuids: args
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
keepass.updateLastUsed(keepass.databaseHash);
|
||||||
|
return response.totpList;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
logError(`getTotp failed: ${err}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.lockDatabase = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ lockSingle ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.LOCK_DATABASE,
|
||||||
|
lockSingle: lockSingle
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
keepass.updateDatabase(tab);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
keepass.isDatabaseClosed = true;
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
logError(`lockDatabase failed: ${err}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.passkeysRegister = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ publicKey, origin ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.PASSKEYS_REGISTER,
|
||||||
|
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||||
|
origin: origin,
|
||||||
|
groupName: page?.settings?.defaultPasskeyGroup,
|
||||||
|
keys: keepass.getCryptoKeys()
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.passkeysGet = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ publicKey, origin ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.PASSKEYS_GET,
|
||||||
|
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||||
|
origin: origin,
|
||||||
|
keys: keepass.getCryptoKeys()
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await keepassClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
browserAction.showDefault(tab);
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
protocol.requestAutotype = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.REQUEST_AUTOTYPE,
|
||||||
|
search: getTopLevelDomainFromUrl(args[0])
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
return response?.result;
|
||||||
|
} catch (err) {
|
||||||
|
logError(`requestAutotype failed: ${err}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.testAssociationFromDatabaseStatuses = async function(tab, args = []) {
|
||||||
|
const databaseStatuses = await protocol.getDatabaseStatuses(tab, args);
|
||||||
|
console.log(databaseStatuses);
|
||||||
|
if (!databaseStatuses) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
areAllLocked: true,
|
||||||
|
associationNeeded: false,
|
||||||
|
databaseHash: undefined,
|
||||||
|
isAnyAssociated: false,
|
||||||
|
isCurrentLocked: true
|
||||||
|
};
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current association status
|
||||||
|
keepass.associated.hash = currentDatabaseStatus[0]?.hash;
|
||||||
|
keepass.associated.value = isCurrentAssociated;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
result.isCurrentLocked = isCurrentLocked;
|
||||||
|
|
||||||
|
keepass.databaseStatuses = databaseStatuses;
|
||||||
|
keepass.databaseAssociationStatuses = result;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol.updateCredentials = async function(tab, args = []) {
|
||||||
|
if (!keepass.isConnected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
action: kpActions.CREATE_CREDENTIALS,
|
||||||
|
keys: protocol.getCurrentKey(),
|
||||||
|
login: username,
|
||||||
|
password: password,
|
||||||
|
submitUrl: url,
|
||||||
|
url: url,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entryId) {
|
||||||
|
messageData.uuid = entryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entryId && page.settings.downloadFaviconAfterSave) {
|
||||||
|
messageData.downloadFavicon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group && groupUuid) {
|
||||||
|
messageData.group = group;
|
||||||
|
messageData.groupUuid = groupUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await protocolClient.sendMessage(tab, messageData);
|
||||||
|
if (response) {
|
||||||
|
keepass.updateLastUsed(keepass.databaseHash);
|
||||||
|
|
||||||
|
if (response?.result === true) {
|
||||||
|
return entryId ? AddCredentials.UPDATED : AddCredentials.CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddCredentials.CANCELED;
|
||||||
|
} else {
|
||||||
|
return AddCredentials.ERROR;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logError(`updateCredentials failed: ${err}`);
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gets the key only from the current active database
|
||||||
|
protocol.getCurrentKey = function() {
|
||||||
|
const [ id, key ] = keepass.getCryptoKey();
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
key: key
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
316
keepassxc-browser/background/protocolClient.js
Normal file
316
keepassxc-browser/background/protocolClient.js
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const protocolBuffer = {
|
||||||
|
buffer: [],
|
||||||
|
|
||||||
|
addMessage(message) {
|
||||||
|
this.buffer.push(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Returns corresponding message from the response. If the response is an error,
|
||||||
|
// return the first matching action from the buffer.
|
||||||
|
getMessage(response) {
|
||||||
|
const isError = Boolean(!response.nonce && response.error && response.errorCode);
|
||||||
|
return this.buffer.find(message => {
|
||||||
|
if (message.request.requestID === response.requestID
|
||||||
|
|| (isError && message.request?.action === response?.action)) {
|
||||||
|
// Cancel timeout
|
||||||
|
if (message.enableTimeout) {
|
||||||
|
message.cancelTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMessage(message) {
|
||||||
|
const index = this.buffer.indexOf(message);
|
||||||
|
if (index >= 0 && index < this.buffer.length) {
|
||||||
|
this.buffer.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Basic class for a message to be sent. The Promise inside the class will be resolved when
|
||||||
|
// the response to the message is received.
|
||||||
|
class ProtocolMessage {
|
||||||
|
constructor(request, enableTimeout, timeoutValue) {
|
||||||
|
this.enableTimeout = enableTimeout;
|
||||||
|
this.request = request;
|
||||||
|
this.timeout = undefined;
|
||||||
|
|
||||||
|
this.promise = new Promise((resolve, reject) => {
|
||||||
|
this.reject = reject;
|
||||||
|
this.resolve = resolve;
|
||||||
|
|
||||||
|
const messageTimeout = timeoutValue || keepassClient.messageTimeout;
|
||||||
|
|
||||||
|
// Handle timeout
|
||||||
|
if (this.enableTimeout) {
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
// The error is action timeout if action is not change-public-keys
|
||||||
|
let error = kpErrors.ACTION_TIMEOUT;
|
||||||
|
if (request.action === kpActions.CHANGE_PUBLIC_KEYS) {
|
||||||
|
error = kpErrors.TIMEOUT_OR_NOT_CONNECTED;
|
||||||
|
keepass.isKeePassXCAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
action: request.action,
|
||||||
|
error: kpErrors.getError(error),
|
||||||
|
errorCode: error
|
||||||
|
});
|
||||||
|
}, messageTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTimeout() {
|
||||||
|
this.enableTimeout = false;
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Protocol V2
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const protocolClient = {};
|
||||||
|
protocolClient.keySize = 24;
|
||||||
|
protocolClient.messageTimeout = 500; // Milliseconds
|
||||||
|
protocolClient.nativeHostName = 'org.keepassxc.keepassxc_browser';
|
||||||
|
protocolClient.nativePort = null;
|
||||||
|
|
||||||
|
protocolClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue = protocolClient.messageTimeout) {
|
||||||
|
if (!protocolClient.nativePort) {
|
||||||
|
logError('No native messaging port defined.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = new ProtocolMessage(request, enableTimeout, timeoutValue);
|
||||||
|
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||||
|
protocolBuffer.addMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
protocolClient.nativePort.postMessage(request);
|
||||||
|
|
||||||
|
const response = await message.promise;
|
||||||
|
|
||||||
|
// Remove a timeouted message
|
||||||
|
if (response.error && response?.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||||
|
protocolBuffer.matchAndRemove(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.sendMessage = async function(tab, messageData, enableTimeout = false, triggerUnlock = false) {
|
||||||
|
const nonce = protocolClient.getNonce();
|
||||||
|
const encryptedMessage = protocolClient.encrypt(messageData, nonce);
|
||||||
|
const request = protocolClient.buildRequest(encryptedMessage, nonce, keepass.clientID, triggerUnlock);
|
||||||
|
const response = await protocolClient.sendNativeMessage(request, enableTimeout);
|
||||||
|
const incrementedNonce = protocolClient.incrementedNonce(nonce);
|
||||||
|
|
||||||
|
return protocolClient.handleResponse(response, incrementedNonce, request.requestID, tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.buildRequest = function(encryptedMessage, nonce, clientID, triggerUnlock = false) {
|
||||||
|
const request = {
|
||||||
|
message: encryptedMessage,
|
||||||
|
nonce: nonce,
|
||||||
|
clientID: clientID,
|
||||||
|
requestID: protocolClient.getRequestId()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (triggerUnlock) {
|
||||||
|
request.triggerUnlock = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.handleNativeMessage = async function(response) {
|
||||||
|
// Parse through the message buffer to find the corresponding Promise.
|
||||||
|
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||||
|
const message = protocolBuffer.getMessage(response);
|
||||||
|
if (message) {
|
||||||
|
message.resolve(response);
|
||||||
|
protocolBuffer.removeMessage(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLogMessage('Corresponding request not found in the message buffer for response: ', response);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verifies nonces, decrypts and parses the response
|
||||||
|
protocolClient.handleResponse = function(response, incrementedNonce, requestID, tab) {
|
||||||
|
if (response.message && protocolClient.verifyNonce(response, incrementedNonce)) {
|
||||||
|
const res = protocolClient.decrypt(response.message, response.nonce);
|
||||||
|
if (!res) {
|
||||||
|
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||||
|
protocolBuffer.matchAndRemove({ requestID: requestID });
|
||||||
|
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);
|
||||||
|
protocolBuffer.matchAndRemove({ requestID: requestID });
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.verifyNonce = function(response, nonce) {
|
||||||
|
if (!response.nonce) {
|
||||||
|
logError('No nonce in response');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!protocolClient.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();
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.getPublicConnectionKey = function() {
|
||||||
|
return nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.generateIdKey = function() {
|
||||||
|
const idKeyPair = nacl.box.keyPair();
|
||||||
|
return nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
protocolClient.generateClientId = function() {
|
||||||
|
return nacl.util.encodeBase64(nacl.randomBytes(protocolClient.keySize));
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// 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();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic response handling
|
||||||
|
if (response.action === kpActions.CHANGE_PUBLIC_KEYS || !keepass.protocolV2) {
|
||||||
|
keepassClient.handleNativeMessage(response);
|
||||||
|
} else {
|
||||||
|
protocolClient.handleNativeMessage(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -43,6 +43,13 @@ const showNotification = function(message) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AddCredentials = {
|
||||||
|
CANCELED: 0,
|
||||||
|
CREATED: 1,
|
||||||
|
ERROR: 2,
|
||||||
|
UPDATED: 3
|
||||||
|
};
|
||||||
|
|
||||||
const AssociatedAction = {
|
const AssociatedAction = {
|
||||||
NOT_ASSOCIATED: 0,
|
NOT_ASSOCIATED: 0,
|
||||||
ASSOCIATED: 1,
|
ASSOCIATED: 1,
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ kpxcBanner.create = async function(credentials = {}) {
|
||||||
kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
||||||
const saveToDefaultGroup = async function(creds) {
|
const saveToDefaultGroup = async function(creds) {
|
||||||
const args = [ creds.username, creds.password, creds.url ];
|
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);
|
kpxcBanner.verifyResult(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -202,7 +202,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
||||||
// Create a new group
|
// Create a new group
|
||||||
const newGroup = await sendMessage('create_new_group', [ result.defaultGroup ]);
|
const newGroup = await sendMessage('create_new_group', [ result.defaultGroup ]);
|
||||||
if (newGroup.name && newGroup.uuid) {
|
if (newGroup.name && newGroup.uuid) {
|
||||||
const res = await sendMessage('add_credentials', [
|
const res = await sendMessage('create_credentials', [
|
||||||
credentials.username,
|
credentials.username,
|
||||||
credentials.password,
|
credentials.password,
|
||||||
credentials.url,
|
credentials.url,
|
||||||
|
|
@ -218,7 +218,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await sendMessage('add_credentials', [
|
const res = await sendMessage('create_credentials', [
|
||||||
credentials.username,
|
credentials.username,
|
||||||
credentials.password,
|
credentials.password,
|
||||||
credentials.url,
|
credentials.url,
|
||||||
|
|
@ -256,7 +256,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await sendMessage('add_credentials', [
|
const res = await sendMessage('create_credentials', [
|
||||||
credentials.username,
|
credentials.username,
|
||||||
credentials.password,
|
credentials.password,
|
||||||
credentials.url,
|
credentials.url,
|
||||||
|
|
@ -341,7 +341,7 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
|
||||||
args: [ url, '', true ] // Sets triggerUnlock to true
|
args: [ url, '', true ] // Sets triggerUnlock to true
|
||||||
}).then(async creds => {
|
}).then(async creds => {
|
||||||
if (!creds || creds.length !== credentials.list.length) {
|
if (!creds || creds.length !== credentials.list.length) {
|
||||||
kpxcBanner.verifyResult('error');
|
kpxcBanner.verifyResult(AddCredentials.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,21 +367,21 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
|
||||||
};
|
};
|
||||||
|
|
||||||
kpxcBanner.verifyResult = async function(code) {
|
kpxcBanner.verifyResult = async function(code) {
|
||||||
if (code === 'error') {
|
if (code === AddCredentials.ERROR) {
|
||||||
kpxcUI.createNotification('error', tr('rememberErrorCannotSaveCredentials'));
|
kpxcUI.createNotification('error', tr('rememberErrorCannotSaveCredentials'));
|
||||||
} else if (code === 'created') {
|
} else if (code === AddCredentials.CREATED) {
|
||||||
kpxcUI.createNotification(
|
kpxcUI.createNotification(
|
||||||
'success',
|
'success',
|
||||||
tr('rememberCredentialsSaved', kpxcBanner.credentials.username || tr('rememberEmptyUsername')),
|
tr('rememberCredentialsSaved', kpxcBanner.credentials.username || tr('rememberEmptyUsername')),
|
||||||
);
|
);
|
||||||
await kpxc.retrieveCredentials(true); // Forced reload
|
await kpxc.retrieveCredentials(true); // Forced reload
|
||||||
} else if (code === 'updated') {
|
} else if (code === AddCredentials.UPDATED) {
|
||||||
kpxcUI.createNotification(
|
kpxcUI.createNotification(
|
||||||
'success',
|
'success',
|
||||||
tr('rememberCredentialsUpdated', kpxcBanner.credentials.username || tr('rememberEmptyUsername')),
|
tr('rememberCredentialsUpdated', kpxcBanner.credentials.username || tr('rememberEmptyUsername')),
|
||||||
);
|
);
|
||||||
await kpxc.retrieveCredentials(true); // Forced reload
|
await kpxc.retrieveCredentials(true); // Forced reload
|
||||||
} else if (code === 'canceled') {
|
} else if (code === AddCredentials.CANCELED) {
|
||||||
kpxcUI.createNotification('warning', tr('rememberCredentialsNotSaved'));
|
kpxcUI.createNotification('warning', tr('rememberCredentialsNotSaved'));
|
||||||
} else {
|
} else {
|
||||||
kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
|
kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
|
||||||
|
|
|
||||||
|
|
@ -144,24 +144,39 @@ kpxcFill.fillTOTPFromUuid = async function(el, uuid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totpFound = false;
|
||||||
if (user.totp?.length > 0) {
|
if (user.totp?.length > 0) {
|
||||||
|
const protocolV2 = await sendMessage('is_protocol_v2');
|
||||||
|
|
||||||
// Retrieve a new TOTP value
|
// Retrieve a new TOTP value
|
||||||
const totp = await sendMessage('get_totp', [ user.uuid, user.totp ]);
|
const totp = await sendMessage('get_totp', (protocolV2 ? [ user.uuid ] : [ user.uuid, user.totp ]));
|
||||||
if (!totp) {
|
if (!totp) {
|
||||||
kpxcUI.createNotification('warning', tr('credentialsNoTOTPFound'));
|
kpxcUI.createNotification('warning', tr('credentialsNoTOTPFound'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
kpxcFill.setTOTPValue(el, totp);
|
if (protocolV2) {
|
||||||
|
const result = totp.find(t => t.uuid === uuid);
|
||||||
|
kpxcFill.setTOTPValue(el, result?.totp);
|
||||||
|
} else {
|
||||||
|
kpxcFill.setTOTPValue(el, totp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
} else if (user.stringFields?.length > 0) {
|
} else if (user.stringFields?.length > 0) {
|
||||||
const stringFields = user.stringFields;
|
const stringFields = user.stringFields;
|
||||||
for (const s of stringFields) {
|
for (const s of stringFields) {
|
||||||
const val = s['KPH: {TOTP}'];
|
const val = s['KPH: {TOTP}'];
|
||||||
if (val) {
|
if (val) {
|
||||||
kpxcFill.setTOTPValue(el, val);
|
kpxcFill.setTOTPValue(el, val);
|
||||||
|
totpFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!totpFound) {
|
||||||
|
kpxcUI.createNotification('warning', tr('credentialsNoTOTPFound'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set normal or segmented TOTP value
|
// Set normal or segmented TOTP value
|
||||||
|
|
@ -251,7 +266,7 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
|
||||||
if (combination.password.maxLength
|
if (combination.password.maxLength
|
||||||
&& combination.password.maxLength > 0
|
&& combination.password.maxLength > 0
|
||||||
&& selectedCredentials.password.length > combination.password.maxLength) {
|
&& selectedCredentials.password.length > combination.password.maxLength) {
|
||||||
kpxcUI.createNotification('warning', tr('errorMessagePaswordLengthExceeded'));
|
kpxcUI.createNotification('warning', tr('errorMessagePasswordLengthExceeded'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent filling password to plain text input field
|
// Prevent filling password to plain text input field
|
||||||
|
|
@ -292,6 +307,12 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
|
||||||
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
||||||
|
|
||||||
await kpxcFill.performAutoSubmit(combination, skipAutoSubmit);
|
await kpxcFill.performAutoSubmit(combination, skipAutoSubmit);
|
||||||
|
|
||||||
|
// Auto-lock database when requested
|
||||||
|
if (await sendMessage('page_get_auto_lock_requested')) {
|
||||||
|
await sendMessage('page_clear_auto_lock_requested');
|
||||||
|
sendMessage('lock_database');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fills StringFields defined in Custom Fields
|
// Fills StringFields defined in Custom Fields
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const sendMessage = async function(action, args) {
|
||||||
* The main content script object.
|
* The main content script object.
|
||||||
*/
|
*/
|
||||||
const kpxc = {};
|
const kpxc = {};
|
||||||
|
kpxc.associationStatus = undefined;
|
||||||
kpxc.combinations = [];
|
kpxc.combinations = [];
|
||||||
kpxc.credentials = [];
|
kpxc.credentials = [];
|
||||||
kpxc.databaseState = DatabaseState.DISCONNECTED;
|
kpxc.databaseState = DatabaseState.DISCONNECTED;
|
||||||
|
|
@ -23,8 +24,8 @@ kpxc.improvedFieldDetectionEnabledForPage = false;
|
||||||
kpxc.inputs = [];
|
kpxc.inputs = [];
|
||||||
kpxc.settings = {};
|
kpxc.settings = {};
|
||||||
kpxc.singleInputEnabledForPage = false;
|
kpxc.singleInputEnabledForPage = false;
|
||||||
kpxc.submitUrl = null;
|
kpxc.submitUrl = undefined;
|
||||||
kpxc.url = null;
|
kpxc.url = undefined;
|
||||||
|
|
||||||
// Add page to Site Preferences with a selected option enabled. Set from the popup.
|
// Add page to Site Preferences with a selected option enabled. Set from the popup.
|
||||||
kpxc.addToSitePreferences = async function(optionName, addWildcard = false) {
|
kpxc.addToSitePreferences = async function(optionName, addWildcard = false) {
|
||||||
|
|
@ -111,6 +112,7 @@ kpxc.createCombination = async function(activeElement, passOnly) {
|
||||||
|
|
||||||
// Switch credentials if database is changed or closed
|
// Switch credentials if database is changed or closed
|
||||||
kpxc.detectDatabaseChange = async function(response) {
|
kpxc.detectDatabaseChange = async function(response) {
|
||||||
|
kpxc.associationStatus = response?.associateResult;
|
||||||
kpxc.databaseState = DatabaseState.LOCKED;
|
kpxc.databaseState = DatabaseState.LOCKED;
|
||||||
kpxc.clearAllFromPage();
|
kpxc.clearAllFromPage();
|
||||||
kpxcIcons.switchIcons();
|
kpxcIcons.switchIcons();
|
||||||
|
|
@ -120,7 +122,8 @@ kpxc.detectDatabaseChange = async function(response) {
|
||||||
_called.retrieveCredentials = false;
|
_called.retrieveCredentials = false;
|
||||||
const settings = await sendMessage('load_settings');
|
const settings = await sendMessage('load_settings');
|
||||||
kpxc.settings = settings;
|
kpxc.settings = settings;
|
||||||
kpxc.databaseState = DatabaseState.UNLOCKED;
|
kpxc.databaseState = response?.associateResult?.areAllLocked
|
||||||
|
? DatabaseState.LOCKED : DatabaseState.UNLOCKED;
|
||||||
|
|
||||||
await kpxc.initCredentialFields();
|
await kpxc.initCredentialFields();
|
||||||
kpxcIcons.switchIcons();
|
kpxcIcons.switchIcons();
|
||||||
|
|
@ -128,7 +131,7 @@ kpxc.detectDatabaseChange = async function(response) {
|
||||||
// If user has requested a manual fill through context menu the actual credential filling
|
// 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.
|
// is handled here when the opened database has been regognized. It's not a pretty hack.
|
||||||
const manualFill = await sendMessage('page_get_manual_fill');
|
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 kpxcFill.fillInFromActiveElement(manualFill === ManualFill.PASSWORD);
|
||||||
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
||||||
}
|
}
|
||||||
|
|
@ -385,6 +388,16 @@ kpxc.initCredentialFields = async function() {
|
||||||
|
|
||||||
await kpxcIcons.initIcons(kpxc.combinations);
|
await kpxcIcons.initIcons(kpxc.combinations);
|
||||||
|
|
||||||
|
// TODO: In optimal case, this should only trigger when a database is opened. How to detect that?
|
||||||
|
// Protocol V2
|
||||||
|
if (kpxc.associationStatus) {
|
||||||
|
if (!kpxc.associationStatus.isCurrentLocked) {
|
||||||
|
await kpxc.retrieveCredentials();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol V1
|
||||||
if (kpxc.databaseState === DatabaseState.UNLOCKED) {
|
if (kpxc.databaseState === DatabaseState.UNLOCKED) {
|
||||||
await kpxc.retrieveCredentials();
|
await kpxc.retrieveCredentials();
|
||||||
}
|
}
|
||||||
|
|
@ -828,6 +841,8 @@ kpxc.updateDatabaseState = async function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Updates the TOTP Autocomplete Menu
|
// Updates the TOTP Autocomplete Menu
|
||||||
|
// TODO: Check this against https://github.com/keepassxreboot/keepassxc-browser/compare/develop...feature/protocol_v2
|
||||||
|
// Which one is the correct implementation?
|
||||||
kpxc.updateTOTPList = async function() {
|
kpxc.updateTOTPList = async function() {
|
||||||
let uuid = await sendMessage('page_get_login_id');
|
let uuid = await sendMessage('page_get_login_id');
|
||||||
if (uuid === undefined || kpxc.credentials.length === 0) {
|
if (uuid === undefined || kpxc.credentials.length === 0) {
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ const iconClicked = async function(field, icon) {
|
||||||
if (kpxc.databaseState !== DatabaseState.UNLOCKED) {
|
if (kpxc.databaseState !== DatabaseState.UNLOCKED) {
|
||||||
// Triggers database unlock
|
// Triggers database unlock
|
||||||
await sendMessage('page_set_manual_fill', ManualFill.BOTH);
|
await sendMessage('page_set_manual_fill', ManualFill.BOTH);
|
||||||
|
// 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
|
await sendMessage('get_database_hash', [ false, true ]); // Set triggerUnlock to true
|
||||||
field.focus();
|
field.focus();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,37 @@ code {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle {
|
||||||
|
display: none;
|
||||||
|
height: 31px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpxc-dropdown-menu {
|
||||||
|
border: var(--kpxc-card-border-color);
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2) !important;
|
||||||
|
overflow: hidden;;
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 40px;
|
||||||
|
z-index: 2147483646;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpxc-dropdown-item {
|
||||||
|
background: var(--kpxc-background-color);
|
||||||
|
color: var(--kpxc-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpxc-dropdown-item:hover {
|
||||||
|
background-color: #28a745 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
#list {
|
#list {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -196,6 +227,15 @@ code {
|
||||||
background-color: var(--kpxc-table-hover-color) !important;
|
background-color: var(--kpxc-table-hover-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kpxc-dropdown-menu {
|
||||||
|
border: var(--kpxc-card-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpxc-dropdown-item {
|
||||||
|
background: var(--kpxc-background-color);
|
||||||
|
color: var(--kpxc-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
span.bg-success {
|
span.bg-success {
|
||||||
background-color: var(--kpxc-table-hover-color) !important;
|
background-color: var(--kpxc-table-hover-color) !important;
|
||||||
color: var(--kpxc-text-color) !important;
|
color: var(--kpxc-text-color) !important;
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,14 @@
|
||||||
</button>
|
</button>
|
||||||
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="lock-button-area">
|
<div class="btn-group lock-button-area">
|
||||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase">
|
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
|
||||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
<select id="dropdown-button" class="btn btn-sm btn-danger dropdown-toggle" style="display: none"></select>
|
||||||
</button>
|
</div>
|
||||||
|
<div class="kpxc-dropdown-menu" style="display: none">
|
||||||
|
<div class="kpxc-dropdown-item">
|
||||||
|
<span id="kpxc-dropdown-item" data-i18n="lockAllDatabases"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ HTMLElement.prototype.hide = function() {
|
||||||
this.style.display = 'none';
|
this.style.display = 'none';
|
||||||
};
|
};
|
||||||
|
|
||||||
function statusResponse(r) {
|
function statusResponse(status) {
|
||||||
$('#initial-state').hide();
|
$('#initial-state').hide();
|
||||||
$('#error-encountered').hide();
|
$('#error-encountered').hide();
|
||||||
$('#need-reconfigure').hide();
|
$('#need-reconfigure').hide();
|
||||||
|
|
@ -21,43 +21,77 @@ function statusResponse(r) {
|
||||||
$('#getting-started-guide').hide();
|
$('#getting-started-guide').hide();
|
||||||
$('#database-not-opened').hide();
|
$('#database-not-opened').hide();
|
||||||
|
|
||||||
if (!r.keePassXCAvailable) {
|
if (!status.keePassXCAvailable) {
|
||||||
$('#error-message').textContent = r.error;
|
$('#error-message').textContent = status.error;
|
||||||
$('#error-encountered').show();
|
$('#error-encountered').show();
|
||||||
|
|
||||||
if (r.showGettingStartedGuideAlert) {
|
if (status.showGettingStartedGuideAlert) {
|
||||||
$('#getting-started-guide').show();
|
$('#getting-started-guide').show();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.showTroubleshootingGuideAlert && reloadCount >= 2) {
|
if (status.showTroubleshootingGuideAlert && reloadCount >= 2) {
|
||||||
$('#troubleshooting-guide').show();
|
$('#troubleshooting-guide').show();
|
||||||
} else {
|
} else {
|
||||||
$('#troubleshooting-guide').hide();
|
$('#troubleshooting-guide').hide();
|
||||||
}
|
}
|
||||||
} else if (r.keePassXCAvailable && r.databaseClosed) {
|
|
||||||
$('#database-error-message').textContent = r.error;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only supported with Protocol V2
|
||||||
|
if (status.protocolV2
|
||||||
|
&& status.databaseAssociationStatuses
|
||||||
|
&& Object.keys(status.databaseAssociationStatuses).length > 0) {
|
||||||
|
// 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();
|
||||||
|
showDropdownButton(status.protocolV2);
|
||||||
|
|
||||||
|
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();
|
$('#database-not-opened').show();
|
||||||
} else if (!r.configured) {
|
} else if (!status.configured) {
|
||||||
$('#not-configured').show();
|
$('#not-configured').show();
|
||||||
} else if (r.encryptionKeyUnrecognized) {
|
} else if (status.encryptionKeyUnrecognized) {
|
||||||
$('#need-reconfigure').show();
|
$('#need-reconfigure').show();
|
||||||
$('#need-reconfigure-message').textContent = r.error;
|
$('#need-reconfigure-message').textContent = status.error;
|
||||||
} else if (!r.associated) {
|
} else if (!status.associated) {
|
||||||
$('#need-reconfigure').show();
|
$('#need-reconfigure').show();
|
||||||
$('#need-reconfigure-message').textContent = r.error;
|
$('#need-reconfigure-message').textContent = status.error;
|
||||||
} else if (r.error) {
|
} else if (status.error) {
|
||||||
$('#error-encountered').show();
|
$('#error-encountered').show();
|
||||||
$('#error-message').textContent = r.error;
|
$('#error-message').textContent = status.error;
|
||||||
} else {
|
} else {
|
||||||
$('#configured-and-associated').show();
|
$('#configured-and-associated').show();
|
||||||
$('#associated-identifier').textContent = r.identifier;
|
$('#associated-identifier').textContent = status.identifier;
|
||||||
$('#lock-database-button').show();
|
$('#lock-database-button').show();
|
||||||
|
|
||||||
if (r.usernameFieldDetected) {
|
if (status.usernameFieldDetected) {
|
||||||
$('#username-field-detected').show();
|
$('#username-field-detected').show();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.iframeDetected) {
|
if (status.iframeDetected) {
|
||||||
$('#iframe-detected').show();
|
$('#iframe-detected').show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,9 +164,11 @@ const sendMessageToTab = async function(message) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#lock-database-button').addEventListener('click', async () => {
|
$('#lock-database-button').addEventListener('click', async () => {
|
||||||
statusResponse(await browser.runtime.sendMessage({
|
statusResponse(await lockDatabase());
|
||||||
action: 'lock_database'
|
});
|
||||||
}));
|
|
||||||
|
$('.kpxc-dropdown-item').addEventListener('click', async () => {
|
||||||
|
statusResponse(await lockDatabase(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#username-only-button').addEventListener('click', async () => {
|
$('#username-only-button').addEventListener('click', async () => {
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,40 @@ async function getLoginData() {
|
||||||
return logins;
|
return logins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function lockDatabase(lockSingle = true) {
|
||||||
|
return await browser.runtime.sendMessage({
|
||||||
|
action: 'lock_database',
|
||||||
|
args: [ lockSingle ]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Show the dropdown button if protocol is supported
|
||||||
|
async function showDropdownButton(isV2) {
|
||||||
|
const isProtocolV2 = isV2 || await browser.runtime.sendMessage({ action: 'is_protocol_v2' });
|
||||||
|
if (isProtocolV2) {
|
||||||
|
$('.lock-button-area')?.classList.add('btn-group');
|
||||||
|
$('#dropdown-button')?.show();
|
||||||
|
} else {
|
||||||
|
$('.lock-button-area')?.classList.remove('btn-group');
|
||||||
|
$('#dropdown-button')?.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDropdownButton() {
|
||||||
|
$('.lock-button-area')?.classList.remove('btn-group');
|
||||||
|
$('#dropdown-button')?.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideElementsOnDatabaseLock() {
|
||||||
|
$('.credentials').hide();
|
||||||
|
$('#database-not-opened').show();
|
||||||
|
$('#lock-database-button').hide();
|
||||||
|
$('#dropdown-button').hide();
|
||||||
|
$('#btn-dismiss')?.hide();
|
||||||
|
$('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened');
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) {
|
if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) {
|
||||||
await initSettings();
|
await initSettings();
|
||||||
|
|
@ -60,7 +94,39 @@ async function getLoginData() {
|
||||||
document.addEventListener('DOMContentLoaded', initSettings);
|
document.addEventListener('DOMContentLoaded', initSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', function(e) {
|
||||||
|
if (!e.isTrusted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.id !== 'kpxc-dropdown-item' && e.target.id !== 'dropdown-button') {
|
||||||
|
$('.kpxc-dropdown-menu')?.hide();
|
||||||
|
$('#dropdown-button').style.borderBottomRightRadius = '4px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
updateAvailableResponse(await browser.runtime.sendMessage({
|
updateAvailableResponse(await browser.runtime.sendMessage({
|
||||||
action: 'update_available_keepassxc'
|
action: 'update_available_keepassxc'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
$('#dropdown-button').addEventListener('click', (e) => {
|
||||||
|
const dropdownMenu = $('.kpxc-dropdown-menu');
|
||||||
|
if (!dropdownMenu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropdownMenu.style.display === 'none') {
|
||||||
|
dropdownMenu.show();
|
||||||
|
$('#dropdown-button').style.borderBottomRightRadius = '0px';
|
||||||
|
} else {
|
||||||
|
dropdownMenu.hide();
|
||||||
|
$('#dropdown-button').style.borderBottomRightRadius = '4px';
|
||||||
|
}
|
||||||
|
|
||||||
|
e.target.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.kpxc-dropdown-item').addEventListener('click', () => {
|
||||||
|
$('.kpxc-dropdown-menu')?.hide();
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,14 @@
|
||||||
</button>
|
</button>
|
||||||
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="lock-button-area">
|
<div class="btn-group lock-button-area">
|
||||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase">
|
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
|
||||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
<select id="dropdown-button" class="btn btn-sm btn-danger dropdown-toggle" style="display: none"></select>
|
||||||
</button>
|
</div>
|
||||||
|
<div class="kpxc-dropdown-menu" style="display: none">
|
||||||
|
<div class="kpxc-dropdown-item">
|
||||||
|
<span id="dropdown-item" data-i18n="lockAllDatabases"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
await initColorTheme();
|
await initColorTheme();
|
||||||
|
|
||||||
$('#lock-database-button').show();
|
$('#lock-database-button').show();
|
||||||
|
await showDropdownButton();
|
||||||
|
|
||||||
const data = await getLoginData();
|
const data = await getLoginData();
|
||||||
const ll = document.getElementById('login-list');
|
const ll = document.getElementById('login-list');
|
||||||
|
|
@ -30,16 +31,14 @@
|
||||||
ll.appendChild(a);
|
ll.appendChild(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#lock-database-button').addEventListener('click', function() {
|
$('#lock-database-button').addEventListener('click', () => {
|
||||||
browser.runtime.sendMessage({
|
lockDatabase();
|
||||||
action: 'lock_database'
|
hideElementsOnDatabaseLock();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.credentials').hide();
|
$('.kpxc-dropdown-item').addEventListener('click', () => {
|
||||||
$('#btn-dismiss').hide();
|
lockDatabase(false);
|
||||||
$('#database-not-opened').show();
|
hideElementsOnDatabaseLock();
|
||||||
$('#lock-database-button').hide();
|
|
||||||
$('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#btn-dismiss').addEventListener('click', async () => {
|
$('#btn-dismiss').addEventListener('click', async () => {
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,14 @@
|
||||||
</button>
|
</button>
|
||||||
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="lock-button-area">
|
<div class="btn-group lock-button-area">
|
||||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase">
|
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
|
||||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
<select id="dropdown-button" class="btn btn-sm btn-danger dropdown-toggle" style="display: none"></select>
|
||||||
</button>
|
</div>
|
||||||
|
<div class="kpxc-dropdown-menu" style="display: none">
|
||||||
|
<div class="kpxc-dropdown-item">
|
||||||
|
<span id="dropdown-item" data-i18n="lockAllDatabases"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('#lock-database-button').show();
|
||||||
|
showDropdownButton();
|
||||||
|
|
||||||
const logins = await getLoginData();
|
const logins = await getLoginData();
|
||||||
const ll = document.getElementById('login-list');
|
const ll = document.getElementById('login-list');
|
||||||
|
|
||||||
|
|
@ -61,20 +64,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#lock-database-button').addEventListener('click', (e) => {
|
$('#lock-database-button').addEventListener('click', (e) => {
|
||||||
browser.runtime.sendMessage({
|
lockDatabase();
|
||||||
action: 'lock_database'
|
hideElementsOnDatabaseLock();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#credentialsList').hide();
|
$('.kpxc-dropdown-item').addEventListener('click', () => {
|
||||||
$('#database-not-opened').show();
|
lockDatabase(false);
|
||||||
$('#lock-database-button').hide();
|
hideElementsOnDatabaseLock();
|
||||||
$('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#reopen-database-button').addEventListener('click', (e) => {
|
$('#reopen-database-button').addEventListener('click', (e) => {
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
action: 'get_status',
|
action: 'get_status',
|
||||||
args: [ false, true ] // Set forcePopup to true
|
args: [ false, true ] // Set triggerUnlock to true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue