mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Merge branch 'develop'
This commit is contained in:
commit
15d6ba97c2
18 changed files with 955 additions and 742 deletions
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!--- Provide a general summary of the issue in the title above -->
|
||||
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
## Current Behavior
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from the current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Debug info
|
||||
<!--- Please copy/paste the browser's JavaScript console messages and errors here -->
|
||||
|
||||
## General Info
|
||||
KeePassXC fork - VERSION
|
||||
keepassxc-browser - VERSION
|
||||
|
||||
Operating system: OS
|
||||
Browser: BROWSER
|
||||
Proxy used: YES/NO
|
||||
32
CHANGELOG
32
CHANGELOG
|
|
@ -1,3 +1,35 @@
|
|||
0.4.2 (27-11-2017)
|
||||
=========================
|
||||
- Fixed HTTP authentication with multiple credentials (credits to smorks)
|
||||
- Fixed error handling when decrypt fails
|
||||
- Fixed database-locked response handling
|
||||
- Fixed nonce increment when encrypting messages
|
||||
|
||||
0.4.1 (18-11-2017)
|
||||
=========================
|
||||
- Added support for the credentials dropdown menu with only password field visible
|
||||
- Fixed jQuery overriding with custom scoped CSS
|
||||
- Fixed non-necessary destroying of autocomplete on autofill
|
||||
|
||||
0.4.0 (13-11-2017)
|
||||
=========================
|
||||
- Fixed showing context menu on password fields with Firefox
|
||||
- Ignore XML files on content scripts (Firefox shows them incorrectly)
|
||||
- UDP features removed as KeePassXC switched them to Unix domain sockets and named pipes
|
||||
|
||||
0.3.9 (04-11-2017)
|
||||
=========================
|
||||
- Removed incorrect timeout waiting on init
|
||||
|
||||
0.3.8 (01-11-2017)
|
||||
=========================
|
||||
- Use browser.storage.local instead of localStorage
|
||||
- Switched some functions to promise
|
||||
|
||||
0.3.7 (22-10-2017)
|
||||
=========================
|
||||
- Improved credentials check (does not use protocol requests for polling)
|
||||
|
||||
0.3.6 (20-10-2017)
|
||||
=========================
|
||||
- Restricted page credentials polling to active tab
|
||||
|
|
|
|||
258
README.md
258
README.md
|
|
@ -1,15 +1,15 @@
|
|||
# keepassxc-browser
|
||||
Chrome extension for [KeePassXC](https://keepassxc.org/) with Native Messaging.
|
||||
Browser extension for [KeePassXC](https://keepassxc.org/) with Native Messaging.
|
||||
|
||||
This is a heavily forked version of [pfn](https://github.com/pfn)'s [chromeIPass](https://github.com/pfn/passifox).
|
||||
Some changes merged also from [smorks'](https://github.com/smorks/keepasshttp-connector) KeePassHttp-Connector fork.
|
||||
For testing purposes, please use following unofficial KeePassXC [release's](https://github.com/varjolintu/keepassxc/releases).
|
||||
For testing purposes, please use only the following unofficial KeePassXC [release's](https://github.com/varjolintu/keepassxc/releases).
|
||||
|
||||
Get the extension for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) or [Chrome/Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/iopaggbpplllidnfmcghoonnokmjoicf).
|
||||
|
||||
The extension is supported with Firefox 55 and newer. If you want to load it as a temporary plugin with Firefox 54 you can just change the minimum version from the manifest file before loading it.
|
||||
The extension is supported with Firefox 55 and newer. If you want to load it as a temporary plugin with Firefox 54 or ESR you can just change the minimum version from the manifest file before loading it.
|
||||
|
||||
Please thee this [wiki page](https://github.com/varjolintu/keepassxc-browser/wiki/Connecting-the-database-with-current-beta-build) for instructions how to configure this KeePassXC fork in order to connect the database correctly.
|
||||
Please thee this [wiki page](hhttps://github.com/varjolintu/keepassxc-browser/wiki/Connecting-the-database-with-keepassxc-browser) for instructions how to configure this KeePassXC fork in order to connect the database correctly.
|
||||
|
||||
## How it works
|
||||
There are two methods which you can use keepassxc-browser to connect to KeePassXC:
|
||||
|
|
@ -17,10 +17,11 @@ There are two methods which you can use keepassxc-browser to connect to KeePassX
|
|||
1. keepassxc-browser communicates directly with KeePassXC via stdin/stdout. This method launches KeePassXC every time you start the browser and closes when you exit.
|
||||
This can cause unsaved changes not to be saved. If you use this method it's important to enable `Automatically save after every change` from KeePassXC's preferences.
|
||||
|
||||
2. keepassxc-browser communicated with KeePassXC through [keepassxc-proxy](https://github.com/varjolintu/keepassxc-proxy) or [keepassxc-proxy-rust](https://github.com/varjolintu/keepassxc-proxy-rust). The proxy handles listening stdin/stdout
|
||||
and transfers these messages through a localhost UDP port 19700 (configurable) to KeePassXC. This means KeePassXC can be used and started normally without inteference from
|
||||
2. keepassxc-browser communicated with KeePassXC through [keepassxc-proxy](https://github.com/varjolintu/keepassxc-proxy). The proxy handles listening stdin/stdout
|
||||
and transfers these messages through Unix domain sockets / named pipes to KeePassXC. This means KeePassXC can be used and started normally without inteference from
|
||||
Native Messaging API. keepassxc-browser starts only the proxy application and there's no risk of shutting down KeePassXC or losing any unsaved changes. keepassxc-proxy
|
||||
is still under development. If you want, you are free to write your own proxy that handles the traffic.
|
||||
is still under development. If you want, you are free to write your own proxy that handles the traffic. You don't need to install keepassxc-proxy separately. It is
|
||||
included in the latest KeePassXC fork. Use it if you want to make your own proxy or improve/extend it.
|
||||
|
||||
## Improvements
|
||||
The following improvements and features have been made after the fork. At this point some features are only available with the KeePassXC fork:
|
||||
|
|
@ -34,248 +35,11 @@ The following improvements and features have been made after the fork. At this p
|
|||
- New buttons, icons and settings page graphics
|
||||
- Redesigned password generator dialog
|
||||
- Password generator supports diceware passphrases and extended ASCII characters
|
||||
- Autocomplete works also when only password fields are visible
|
||||
|
||||
## Protocol
|
||||
|
||||
Transmitting messages between KeePassXC and keepassxc-browser is totally rewritten. This is still under development.
|
||||
Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweetnacl-js) box method and does the following:
|
||||
|
||||
1. keepassxc-browser generates a key pair (with public and secret key) and transfers the public key to KeePassXC
|
||||
2. When KeePassXC receives the public key it generates its own key pair and transfers the public key to keepassxc-browser
|
||||
3. All messages between the browser extension and KeePassXC are now encrypted.
|
||||
4. When keepassxc-browser sends a message it is encrypted with KeePassXC's public key, a random generated nonce and keepassxc-browser's secret key.
|
||||
5. When KeePassXC sends a message it is encrypted with keepassxc-browser's public key etc.
|
||||
6. Databases are stored based on the current public key used with `associate`. A new key pair for data transfer is generated each time keepassxc-browser is launched.
|
||||
|
||||
Encrypted messages are built with these JSON parameters:
|
||||
- action - `test-associate`, `associate`, `get-logins`, `get-logins-count`, `set-login`...
|
||||
- message - Encrypted message, base64 encoded
|
||||
- nonce - 24 bytes long random data, base64 encoded. This must be the same when responding to a request.
|
||||
- clientID - 24 bytes long random data, base64 encoded. This is used to identify different browsers if multiple are used with proxy application.
|
||||
|
||||
### change-public-keys
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "change-public-keys",
|
||||
"publicKey": "<current public key>",
|
||||
"proxyPort": "<UDP port for proxy applications>",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response (success):
|
||||
```javascript
|
||||
{
|
||||
"action": "change-public-keys",
|
||||
"version": "2.2.0",
|
||||
"publicKey": "<host public key>",
|
||||
"success": "true"
|
||||
}
|
||||
```
|
||||
|
||||
### get-databasehash
|
||||
Request (unencrypted):
|
||||
```javascript
|
||||
{
|
||||
"action": "get-databasehash"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"action": "hash",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
```
|
||||
|
||||
### associate
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "associate",
|
||||
"key": "<current public key>"
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "associate",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0",
|
||||
"success": "true",
|
||||
"id": "testclient",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
|
||||
### test-associate
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "test-associate",
|
||||
"id": "<saved database identifier>",
|
||||
"key": "<saved database public key>",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "test-associate",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"id": "testclient",
|
||||
"success": "true"
|
||||
}
|
||||
```
|
||||
|
||||
### generate-password
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "generate-password",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"entries": [
|
||||
{
|
||||
"login": 144,
|
||||
"password": "testclientpassword"
|
||||
}
|
||||
],
|
||||
"success": "true",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
|
||||
### get-logins
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "get-logins",
|
||||
"url": "<snip>",
|
||||
"submitUrl": optional
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "get-logins",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"count": "2",
|
||||
"entries" : [
|
||||
{
|
||||
"login": "user1",
|
||||
"name": "user1",
|
||||
"password": "passwd1"
|
||||
},
|
||||
{
|
||||
"login": "user2",
|
||||
"name": "user2",
|
||||
"password": "passwd2"
|
||||
}],
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"success": "true",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
```
|
||||
|
||||
### set-login
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "set-login",
|
||||
"url": "<snip>",
|
||||
"submitUrl": "<snip>",
|
||||
"id": "testclient",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"login": "user1",
|
||||
"password": "passwd1"
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "set-login",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"count": null,
|
||||
"entries" : null,
|
||||
"error": "",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"success": "true",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
```
|
||||
|
||||
### lock-database
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "lock-database",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success always returns an error, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"action": "lock-database",
|
||||
"errorCode": 1,
|
||||
"error": "Database not opened",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
The details about the messaging protocol used with the browser extension and KeePassXC can be found [here](keepassxc-protocol.md).
|
||||
|
||||
## Licenses
|
||||
|
||||
|
|
@ -315,4 +79,4 @@ Feel free to support this project:
|
|||
- Donate via [PayPal](https://paypal.me/varjolintu)
|
||||
- Donate via Bitcoin: 1LHbD69CcmpLW5hjUXs2MGJhw3GxwqLdw3
|
||||
|
||||
Also consider donating to [KeePassXC](https://flattr.com/submit/auto?fid=x7yqz0&url=https%3A%2F%2Fkeepassxc.org) and passifox teams [(1)](https://github.com/smorks/passifox),[(2)](https://github.com/projectgus/passifox),[(3)](https://github.com/pfn/passifox). They are doing great job.
|
||||
Also consider donating to [KeePassXC](https://flattr.com/submit/auto?fid=x7yqz0&url=https%3A%2F%2Fkeepassxc.org) and passifox teams [(1)](https://github.com/smorks/passifox),[(2)](https://github.com/pfn/passifox). They are doing great job.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
var browserAction = {};
|
||||
const browserAction = {};
|
||||
|
||||
const BLINK_TIMEOUT_DEFAULT = 7500;
|
||||
const BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT = -1;
|
||||
|
|
@ -70,7 +70,7 @@ browserAction.showDefault = function(callback, tab) {
|
|||
iconType: 'normal',
|
||||
popup: 'popup.html'
|
||||
};
|
||||
keepass.isConfigured((response) => {
|
||||
keepass.isConfigured().then((response) => {
|
||||
if (!response || keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable || page.tabs[tab.id].errorMessage) {
|
||||
stackData.iconType = 'cross';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
var kpxcEvent = {};
|
||||
const kpxcEvent = {};
|
||||
|
||||
kpxcEvent.onMessage = function(request, sender, callback) {
|
||||
if (request.action in kpxcEvent.messageHandlers) {
|
||||
|
|
@ -72,7 +72,7 @@ kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) {
|
|||
else {
|
||||
console.log('undefined handler for tab ' + tab.id);
|
||||
}
|
||||
});
|
||||
}).catch((e) => {console.log(e);});
|
||||
};
|
||||
|
||||
kpxcEvent.onShowAlert = function(callback, tab, message) {
|
||||
|
|
@ -100,35 +100,52 @@ kpxcEvent.showStatus = function(configured, tab, callback) {
|
|||
};
|
||||
|
||||
kpxcEvent.onLoadSettings = function(callback, tab) {
|
||||
page.settings = (typeof(localStorage.settings) === 'undefined') ? {} : JSON.parse(localStorage.settings);
|
||||
browser.storage.local.get({'settings': {}}).then((item) => {
|
||||
callback(item.settings);
|
||||
}, (err) => {
|
||||
console.log('error loading settings: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
kpxcEvent.onLoadKeyRing = function(callback, tab) {
|
||||
keepass.keyRing = (typeof(localStorage.keyRing) === 'undefined') ? {} : JSON.parse(localStorage.keyRing);
|
||||
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
|
||||
keepass.associated = {
|
||||
value: false,
|
||||
hash: null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
kpxcEvent.onGetSettings = function(callback, tab) {
|
||||
kpxcEvent.onLoadSettings();
|
||||
callback({ data: page.settings });
|
||||
browser.storage.local.get({'keyRing': {}}).then(function(item) {
|
||||
keepass.keyRing = item.keyRing;
|
||||
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
|
||||
keepass.associated = {
|
||||
"value": false,
|
||||
"hash": null
|
||||
};
|
||||
}
|
||||
callback(item.keyRing);
|
||||
}, (err) => {
|
||||
console.log('error loading keyRing: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
kpxcEvent.onSaveSettings = function(callback, tab, settings) {
|
||||
localStorage.settings = JSON.stringify(settings);
|
||||
kpxcEvent.onLoadSettings();
|
||||
browser.storage.local.set({'settings': settings}).then(function() {
|
||||
kpxcEvent.onLoadSettings(callback, tab);
|
||||
});
|
||||
};
|
||||
|
||||
kpxcEvent.onGetStatus = function(callback, tab) {
|
||||
keepass.testAssociation((response) => {
|
||||
keepass.isConfigured((configured) => {
|
||||
kpxcEvent.onGetStatus = function(callback, tab, internalPoll = false) {
|
||||
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
|
||||
if (!internalPoll) {
|
||||
keepass.testAssociation((response) => {
|
||||
if (!response) {
|
||||
kpxcEvent.showStatus(false, tab, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
keepass.isConfigured().then((configured) => {
|
||||
kpxcEvent.showStatus(configured, tab, callback);
|
||||
});
|
||||
}, tab, true);
|
||||
} else {
|
||||
keepass.isConfigured().then((configured) => {
|
||||
kpxcEvent.showStatus(configured, tab, callback);
|
||||
});
|
||||
}, tab, true);
|
||||
}
|
||||
};
|
||||
|
||||
kpxcEvent.onReconnect = function(callback, tab) {
|
||||
|
|
@ -137,13 +154,13 @@ kpxcEvent.onReconnect = function(callback, tab) {
|
|||
// Add a small timeout after reconnecting. Just to make sure. It's not pretty, I know :(
|
||||
setTimeout(() => {
|
||||
keepass.generateNewKeyPair();
|
||||
keepass.changePublicKeys(tab, (pkRes) => {
|
||||
keepass.changePublicKeys(tab).then((pkRes) => {
|
||||
keepass.getDatabaseHash((gdRes) => {
|
||||
if (gdRes) {
|
||||
keepass.testAssociation((response) => {
|
||||
keepass.isConfigured((configured) => {
|
||||
keepass.isConfigured().then((configured) => {
|
||||
kpxcEvent.showStatus(configured, tab, callback);
|
||||
});
|
||||
}).catch((e) => {console.log(e);});
|
||||
}, tab);
|
||||
}
|
||||
}, null);
|
||||
|
|
@ -152,9 +169,9 @@ kpxcEvent.onReconnect = function(callback, tab) {
|
|||
};
|
||||
|
||||
kpxcEvent.lockDatabase = function(callback, tab) {
|
||||
keepass.lockDatabase((response) => {
|
||||
keepass.lockDatabase(tab).then((response => {
|
||||
kpxcEvent.showStatus(true, tab, callback);
|
||||
}, tab);
|
||||
}));
|
||||
};
|
||||
|
||||
kpxcEvent.onPopStack = function(callback, tab) {
|
||||
|
|
@ -175,12 +192,13 @@ kpxcEvent.onGetConnectedDatabase = function(callback, tab) {
|
|||
};
|
||||
|
||||
kpxcEvent.onGetKeePassXCVersions = function(callback, tab) {
|
||||
if (keepass.currentKeePassXC.version === 0) {
|
||||
keepass.getDatabaseHash((response) => {
|
||||
callback({current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version});
|
||||
if(keepass.currentKeePassXC.version == 0) {
|
||||
keepass.getDatabaseHash((res) => {
|
||||
callback({"current": keepass.currentKeePassXC.version, "latest": keepass.currentKeePassXC.version});
|
||||
}, tab);
|
||||
}
|
||||
callback({current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version});
|
||||
} else {
|
||||
callback({"current": keepass.currentKeePassXC.version, "latest": keepass.currentKeePassXC.version});
|
||||
}
|
||||
};
|
||||
|
||||
kpxcEvent.onCheckUpdateKeePassXC = function(callback, tab) {
|
||||
|
|
@ -240,7 +258,7 @@ kpxcEvent.pageClearLogins = function(callback, tab) {
|
|||
|
||||
kpxcEvent.oldDatabaseHash = 'no-hash';
|
||||
kpxcEvent.checkDatabaseHash = function(callback, tab) {
|
||||
keepass.getDatabaseHash((response) => {
|
||||
keepass.checkDatabaseHash((response) => {
|
||||
callback({old: kpxcEvent.oldDatabaseHash, new: response});
|
||||
kpxcEvent.oldDatabaseHash = response;
|
||||
});
|
||||
|
|
@ -254,7 +272,6 @@ kpxcEvent.messageHandlers = {
|
|||
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
|
||||
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
|
||||
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
|
||||
'get_settings': kpxcEvent.onGetSettings,
|
||||
'get_status': kpxcEvent.onGetStatus,
|
||||
'get_tab_information': kpxcEvent.onGetTabInformation,
|
||||
'load_keyring': kpxcEvent.onLoadKeyRing,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
var httpAuth = httpAuth || {};
|
||||
const httpAuth = {};
|
||||
|
||||
httpAuth.requests = [];
|
||||
httpAuth.pendingCallbacks = [];
|
||||
|
||||
httpAuth.requestCompleted = function(details) {
|
||||
let index = httpAuth.requests.indexOf(details.requestId);
|
||||
if (index > -1) {
|
||||
if (index >= 0) {
|
||||
httpAuth.requests.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
|
@ -23,6 +23,7 @@ httpAuth.handleRequestCallback = function(details, callback) {
|
|||
httpAuth.processPendingCallbacks = function(details, resolve, reject) {
|
||||
if (httpAuth.requests.indexOf(details.requestId) >= 0 || !page.tabs[details.tabId]) {
|
||||
reject({});
|
||||
return;
|
||||
}
|
||||
|
||||
httpAuth.requests.push(details.requestId);
|
||||
|
|
@ -41,10 +42,7 @@ httpAuth.processPendingCallbacks = function(details, resolve, reject) {
|
|||
httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) {
|
||||
// at least one login found --> use first to login
|
||||
if (logins.length > 0) {
|
||||
kpxcEvent.onHTTPAuthPopup(null, { "id": details.tabId }, { "logins": logins, "url": details.searchUrl });
|
||||
//generate popup-list for HTTP Auth usernames + descriptions
|
||||
|
||||
if (page.settings.autoFillAndSend) {
|
||||
if (logins.length == 1 && page.settings.autoFillAndSend) {
|
||||
resolve({
|
||||
authCredentials: {
|
||||
username: logins[0].login,
|
||||
|
|
@ -52,7 +50,7 @@ httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
reject({});
|
||||
kpxcEvent.onHTTPAuthPopup(null, { 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
|
||||
}
|
||||
}
|
||||
// no logins found
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
keepass.convertKeyToKeyRing();
|
||||
page.initSettings();
|
||||
page.initOpenedTabs();
|
||||
keepass.connectToNative();
|
||||
keepass.generateNewKeyPair();
|
||||
keepass.changePublicKeys(null, (pkRes) => {
|
||||
keepass.getDatabaseHash((gdRes) => {}, null);
|
||||
keepass.migrateKeyRing().then(() => {
|
||||
page.initSettings().then(() => {
|
||||
page.initOpenedTabs().then(() => {
|
||||
keepass.connectToNative();
|
||||
keepass.generateNewKeyPair();
|
||||
keepass.changePublicKeys(null).then((pkRes) => {
|
||||
keepass.getDatabaseHash((gdRes) => {}, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Milliseconds for intervall (e.g. to update browserAction)
|
||||
let _interval = 250;
|
||||
const _interval = 250;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -90,17 +92,23 @@ if (browser.webRequest.onAuthRequired) {
|
|||
browser.runtime.onMessage.addListener(kpxcEvent.onMessage);
|
||||
|
||||
const contextMenuItems = [
|
||||
{title: 'Fill &User + Pass', action: 'fill_user_pass'},
|
||||
{title: 'Fill &Pass Only', action: 'fill_pass_only'},
|
||||
{title: 'Show Password &Generator Icons', action: 'activate_password_generator'},
|
||||
{title: '&Save credentials', action: 'remember_credentials'}
|
||||
{title: 'Fill User + Pass', action: 'fill_user_pass'},
|
||||
{title: 'Fill Pass Only', action: 'fill_pass_only'},
|
||||
{title: 'Show Password Generator Icons', action: 'activate_password_generator'},
|
||||
{title: 'Save credentials', action: 'remember_credentials'}
|
||||
];
|
||||
|
||||
let menuContexts = ['editable'];
|
||||
|
||||
if (isFirefox()) {
|
||||
menuContexts.push('password');
|
||||
}
|
||||
|
||||
// Create context menu items
|
||||
for (const item of contextMenuItems) {
|
||||
browser.contextMenus.create({
|
||||
title: item.title,
|
||||
contexts: [ 'editable' ],
|
||||
contexts: menuContexts,
|
||||
onclick: (info, tab) => {
|
||||
browser.tabs.sendMessage(tab.id, {
|
||||
action: item.action
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
var keepass = {};
|
||||
const keepass = {};
|
||||
|
||||
keepass.associated = {'value': false, 'hash': null};
|
||||
keepass.keyPair = {publicKey: null, secretKey: null};
|
||||
|
|
@ -11,7 +11,6 @@ keepass.isDatabaseClosed = false;
|
|||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.isEncryptionKeyUnrecognized = false;
|
||||
keepass.currentKeePassXC = {'version': 0, 'versionParsed': 0};
|
||||
keepass.latestKeePassXC = (typeof(localStorage.latestKeePassXC) === 'undefined') ? {'version': 0, 'versionParsed': 0, 'lastChecked': null} : JSON.parse(localStorage.latestKeePassXC);
|
||||
keepass.requiredKeePassXC = 220;
|
||||
keepass.nativeHostName = 'com.varjolintu.keepassxc_browser';
|
||||
keepass.nativePort = null;
|
||||
|
|
@ -19,10 +18,10 @@ keepass.keySize = 24;
|
|||
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
|
||||
keepass.cacheTimeout = 30 * 1000; // milliseconds
|
||||
keepass.databaseHash = 'no-hash'; //no-hash = KeePassXC is too old and does not return a hash value
|
||||
keepass.keyRing = (typeof(localStorage.keyRing) === 'undefined') ? {} : JSON.parse(localStorage.keyRing);
|
||||
keepass.keyId = 'keepassxc-browser-cryptokey-name';
|
||||
keepass.keyBody = 'keepassxc-browser-key';
|
||||
keepass.messageTimeout = 500; // milliseconds
|
||||
keepass.nonce = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize));
|
||||
|
||||
const kpActions = {
|
||||
SET_LOGIN: 'set-login',
|
||||
|
|
@ -77,6 +76,55 @@ const kpErrors = {
|
|||
}
|
||||
};
|
||||
|
||||
browser.storage.local.get({
|
||||
'latestKeePassXC': {'version': 0, 'versionParsed': 0, 'lastChecked': null},
|
||||
'keyRing': {}}).then((item) => {
|
||||
keepass.latestKeePassXC = item.latestKeePassXC;
|
||||
keepass.keyRing = item.keyRing;
|
||||
});
|
||||
|
||||
keepass.sendNativeMessage = function(request, enableTimeout = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout;
|
||||
let action = request.action;
|
||||
let ev = keepass.nativePort.onMessage;
|
||||
|
||||
let listener = ((port, action) => {
|
||||
let handler = (msg) => {
|
||||
if (msg && msg.action === action) {
|
||||
port.removeListener(handler);
|
||||
if (enableTimeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
resolve(msg);
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev, action);
|
||||
ev.addListener(listener);
|
||||
|
||||
|
||||
// Handle timeouts
|
||||
if (enableTimeout) {
|
||||
timeout = setTimeout(() => {
|
||||
const errorMessage = {
|
||||
action: action,
|
||||
error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED),
|
||||
errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED
|
||||
};
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
ev.removeListener(listener.handler);
|
||||
resolve(errorMessage);
|
||||
}, keepass.messageTimeout);
|
||||
}
|
||||
|
||||
// Send the request
|
||||
if (keepass.nativePort) {
|
||||
keepass.nativePort.postMessage(request);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
keepass.addCredentials = function(callback, tab, username, password, url) {
|
||||
keepass.updateCredentials(callback, tab, null, username, password, url);
|
||||
};
|
||||
|
|
@ -86,8 +134,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
|
|||
page.tabs[tab.id].errorMessage = null;
|
||||
|
||||
keepass.testAssociation((response) => {
|
||||
if (!response)
|
||||
{
|
||||
if (!response) {
|
||||
browserAction.showDefault(null, tab);
|
||||
callback([]);
|
||||
return;
|
||||
|
|
@ -95,7 +142,8 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
|
|||
|
||||
const kpAction = kpActions.SET_LOGIN;
|
||||
const {dbid} = keepass.getCryptoKey();
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
|
||||
let messageData = {
|
||||
action: kpAction,
|
||||
|
|
@ -113,19 +161,22 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
|
|||
const request = {
|
||||
action: kpAction,
|
||||
message: keepass.encrypt(messageData, nonce),
|
||||
nonce: keepass.b64e(nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
console.log(request);
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.sendNativeMessage(request).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
callback(keepass.verifyResponse(parsed, response.nonce) ? 'success' : 'error');
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
callback('error');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
callback(keepass.verifyResponse(parsed, incrementedNonce) ? 'success' : 'error');
|
||||
}
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
|
|
@ -134,7 +185,6 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
|
|||
browserAction.showDefault(null, tab);
|
||||
}
|
||||
});
|
||||
keepass.nativePort.postMessage(request);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -142,8 +192,7 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
|
|||
page.debug('keepass.retrieveCredentials(callback, {1}, {2}, {3}, {4})', tab.id, url, submiturl, forceCallback);
|
||||
|
||||
keepass.testAssociation((response) => {
|
||||
if (!response)
|
||||
{
|
||||
if (!response) {
|
||||
browserAction.showDefault(null, tab);
|
||||
if (forceCallback) {
|
||||
callback([]);
|
||||
|
|
@ -160,7 +209,8 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
|
|||
|
||||
let entries = [];
|
||||
const kpAction = kpActions.GET_LOGINS;
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
const {dbid} = keepass.getCryptoKey();
|
||||
|
||||
let messageData = {
|
||||
|
|
@ -176,32 +226,36 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
|
|||
const request = {
|
||||
action: kpAction,
|
||||
message: keepass.encrypt(messageData, nonce),
|
||||
nonce: keepass.b64e(nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.sendNativeMessage(request).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
|
||||
if (keepass.verifyResponse(parsed, response.nonce)) {
|
||||
entries = parsed.entries;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
if (entries.length === 0) {
|
||||
// questionmark-icon is not triggered, so we have to trigger for the normal symbol
|
||||
browserAction.showDefault(null, tab);
|
||||
}
|
||||
callback(entries);
|
||||
}
|
||||
else {
|
||||
console.log('RetrieveCredentials for ' + url + ' rejected');
|
||||
}
|
||||
page.debug('keepass.retrieveCredentials() => entries.length = {1}', entries.length);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
|
||||
if (keepass.verifyResponse(parsed, incrementedNonce)) {
|
||||
entries = parsed.entries;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
if (entries.length === 0) {
|
||||
// questionmark-icon is not triggered, so we have to trigger for the normal symbol
|
||||
browserAction.showDefault(null, tab);
|
||||
}
|
||||
callback(entries);
|
||||
}
|
||||
else {
|
||||
console.log('RetrieveCredentials for ' + url + ' rejected');
|
||||
}
|
||||
page.debug('keepass.retrieveCredentials() => entries.length = {1}', entries.length);
|
||||
}
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
|
|
@ -210,42 +264,9 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
|
|||
browserAction.showDefault(null, tab);
|
||||
}
|
||||
});
|
||||
keepass.nativePort.postMessage(request);
|
||||
}, tab);
|
||||
};
|
||||
|
||||
// Redirects the callback to a listener (handleReply())
|
||||
keepass.callbackOnId = function(ev, action, tab, callback, enableTimeout = false) {
|
||||
let timeout;
|
||||
let listener = ((port, action) => {
|
||||
let handler = (msg) => {
|
||||
if (msg && msg.action === action) {
|
||||
ev.removeListener(handler);
|
||||
if (enableTimeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
callback(msg);
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev, action, tab, callback);
|
||||
ev.addListener(listener);
|
||||
|
||||
// Handle timeouts
|
||||
if (enableTimeout) {
|
||||
timeout = setTimeout(() => {
|
||||
const errorMessage = {
|
||||
action: action,
|
||||
error: kpErrors.getError(5),
|
||||
errorCode: 5
|
||||
};
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
callback(errorMessage);
|
||||
ev.removeListener(listener.handler);
|
||||
}, keepass.messageTimeout);
|
||||
}
|
||||
};
|
||||
|
||||
keepass.generatePassword = function(callback, tab, forceCallback) {
|
||||
if (!keepass.isConnected) {
|
||||
callback([]);
|
||||
|
|
@ -253,8 +274,7 @@ keepass.generatePassword = function(callback, tab, forceCallback) {
|
|||
}
|
||||
|
||||
keepass.testAssociation((taresponse) => {
|
||||
if (!taresponse)
|
||||
{
|
||||
if (!taresponse) {
|
||||
browserAction.showDefault(null, tab);
|
||||
if (forceCallback) {
|
||||
callback([]);
|
||||
|
|
@ -269,42 +289,46 @@ keepass.generatePassword = function(callback, tab, forceCallback) {
|
|||
|
||||
let passwords = [];
|
||||
const kpAction = kpActions.GENERATE_PASSWORD;
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
nonce: keepass.b64e(nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.sendNativeMessage(request).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (keepass.verifyResponse(parsed, response.nonce)) {
|
||||
if (parsed.entries) {
|
||||
passwords = parsed.entries;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
}
|
||||
else {
|
||||
console.log('No entries returned. Is KeePassXC up-to-date?');
|
||||
}
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
|
||||
if (keepass.verifyResponse(parsed, incrementedNonce)) {
|
||||
if (parsed.entries) {
|
||||
passwords = parsed.entries;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
}
|
||||
else {
|
||||
console.log('GeneratePassword rejected');
|
||||
console.log('No entries returned. Is KeePassXC up-to-date?');
|
||||
}
|
||||
callback(passwords);
|
||||
}
|
||||
else {
|
||||
console.log('GeneratePassword rejected');
|
||||
}
|
||||
callback(passwords);
|
||||
}
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
});
|
||||
keepass.nativePort.postMessage(request);
|
||||
}, tab);
|
||||
};
|
||||
|
||||
|
|
@ -323,8 +347,9 @@ keepass.associate = function(callback, tab) {
|
|||
page.tabs[tab.id].errorMessage = null;
|
||||
|
||||
const kpAction = kpActions.ASSOCIATE;
|
||||
const key = keepass.b64e(keepass.keyPair.publicKey);
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
|
|
@ -334,36 +359,38 @@ keepass.associate = function(callback, tab) {
|
|||
const request = {
|
||||
action: kpAction,
|
||||
message: keepass.encrypt(messageData, nonce),
|
||||
nonce: keepass.b64e(nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.sendNativeMessage(request).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
const id = parsed.id;
|
||||
|
||||
if (!keepass.verifyResponse(parsed, response.nonce)) {
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
}
|
||||
else {
|
||||
keepass.setCryptoKey(id, key); // Save the current public key as id key for the database
|
||||
keepass.associated.value = true;
|
||||
keepass.associated.hash = parsed.hash || 0;
|
||||
}
|
||||
|
||||
browserAction.show(callback, tab);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
const id = parsed.id;
|
||||
|
||||
if (!keepass.verifyResponse(parsed, incrementedNonce)) {
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
}
|
||||
else {
|
||||
keepass.setCryptoKey(id, key); // Save the current public key as id key for the database
|
||||
keepass.associated.value = true;
|
||||
keepass.associated.hash = parsed.hash || 0;
|
||||
}
|
||||
|
||||
browserAction.show(callback, tab);
|
||||
}
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
});
|
||||
keepass.nativePort.postMessage(request);
|
||||
}, tab);
|
||||
};
|
||||
|
||||
|
|
@ -397,7 +424,8 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) {
|
|||
}
|
||||
|
||||
const kpAction = kpActions.TEST_ASSOCIATE;
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
const {dbid, dbkey} = keepass.getCryptoKey();
|
||||
|
||||
if (dbkey === null || dbid === null) {
|
||||
|
|
@ -417,34 +445,38 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) {
|
|||
const request = {
|
||||
action: kpAction,
|
||||
message: keepass.encrypt(messageData, nonce),
|
||||
nonce: keepass.b64e(nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.sendNativeMessage(request, enableTimeout).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.isEncryptionKeyUnrecognized = false;
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keepass.verifyResponse(parsed, response.nonce)) {
|
||||
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 {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
delete page.tabs[tab.id].errorMessage;
|
||||
}
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.isEncryptionKeyUnrecognized = false;
|
||||
|
||||
if (!keepass.verifyResponse(parsed, incrementedNonce)) {
|
||||
const hash = response.hash || 0;
|
||||
keepass.deleteKey(hash);
|
||||
keepass.isEncryptionKeyUnrecognized = true;
|
||||
keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED);
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
else if (!keepass.isAssociated()) {
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
}
|
||||
else {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
delete page.tabs[tab.id].errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -453,7 +485,6 @@ keepass.testAssociation = function(callback, tab, enableTimeout = false) {
|
|||
}
|
||||
callback(keepass.isAssociated());
|
||||
});
|
||||
keepass.nativePort.postMessage(request);
|
||||
}, tab, enableTimeout);
|
||||
};
|
||||
|
||||
|
|
@ -465,11 +496,12 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) {
|
|||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
keepass.changePublicKeys(tab, null);
|
||||
keepass.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_DATABASE_HASH;
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
|
|
@ -485,173 +517,202 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false) {
|
|||
const request = {
|
||||
action: kpAction,
|
||||
message: encrypted,
|
||||
nonce: keepass.b64e(nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.sendNativeMessage(request, enableTimeout).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
callback('no-hash');
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.hash) {
|
||||
const oldDatabaseHash = keepass.databaseHash;
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.databaseHash = parsed.hash || 'no-hash';
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
if (parsed.hash) {
|
||||
const oldDatabaseHash = keepass.databaseHash;
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.databaseHash = parsed.hash || 'no-hash';
|
||||
|
||||
if (oldDatabaseHash && oldDatabaseHash != keepass.databaseHash) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = false;
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
callback(parsed.hash);
|
||||
}
|
||||
else if (parsed.errorCode) {
|
||||
keepass.databaseHash = 'no-hash';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
callback(keepass.databaseHash);
|
||||
if (oldDatabaseHash && oldDatabaseHash != keepass.databaseHash) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = false;
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
callback(parsed.hash);
|
||||
return;
|
||||
}
|
||||
else if (parsed.errorCode) {
|
||||
keepass.databaseHash = 'no-hash';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
callback(keepass.databaseHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
keepass.databaseHash = 'no-hash';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
if (response.message === "") {
|
||||
if (response.message === '') {
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
}
|
||||
else {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
callback(keepass.databaseHash);
|
||||
return;
|
||||
}
|
||||
}, enableTimeout);
|
||||
keepass.nativePort.postMessage(request);
|
||||
};
|
||||
|
||||
keepass.changePublicKeys = function(tab, callback) {
|
||||
if (!keepass.isConnected) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
|
||||
const key = keepass.b64e(keepass.keyPair.publicKey);
|
||||
let nonce = nacl.randomBytes(keepass.keySize);
|
||||
nonce = keepass.b64e(nonce);
|
||||
keepass.clientID = keepass.b64e(nacl.randomBytes(keepass.keySize));
|
||||
|
||||
const message = {
|
||||
action: kpAction,
|
||||
publicKey: key,
|
||||
proxyPort: (page.settings.port ? page.settings.port : 19700),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
|
||||
if (!keepass.verifyKeyResponse(response, key, nonce)) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
console.log('Server public key: ' + keepass.b64e(keepass.serverPublicKey));
|
||||
}
|
||||
callback(true);
|
||||
});
|
||||
keepass.nativePort.postMessage(message);
|
||||
};
|
||||
|
||||
keepass.lockDatabase = function(callback, tab, forceCallback) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
keepass.changePublicKeys = function(tab, enableTimeout = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
reject(false);
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = nacl.randomBytes(keepass.keySize);
|
||||
const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize));
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
const request = {
|
||||
action: kpAction,
|
||||
publicKey: key,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
message: keepass.encrypt(messageData, nonce),
|
||||
nonce: keepass.b64e(nonce),
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
keepass.sendNativeMessage(request, enableTimeout).then((response) => {
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
|
||||
if (!keepass.verifyKeyResponse(response, key, incrementedNonce)) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
reject(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
console.log('Server public key: ' + nacl.util.encodeBase64(keepass.serverPublicKey));
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
keepass.lockDatabase = function(tab) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
reject(false);
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = keepass.getNonce();
|
||||
const incrementedNonce = keepass.incrementedNonce(nonce);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
message: keepass.encrypt(messageData, nonce),
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
keepass.sendNativeMessage(request).then((response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
keepass.callbackOnId(keepass.nativePort.onMessage, kpAction, tab, (response) => {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepass.decrypt(response.message, response.nonce);
|
||||
if (res) {
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
|
||||
if (keepass.verifyResponse(parsed, response.nonce)) {
|
||||
if (keepass.verifyResponse(parsed, incrementedNonce)) {
|
||||
keepass.isDatabaseClosed = true;
|
||||
|
||||
// Display error message in the popup
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
callback(false);
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
callback(false);
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
keepass.nativePort.postMessage(request);
|
||||
};
|
||||
|
||||
keepass.generateNewKeyPair = function() {
|
||||
keepass.keyPair = nacl.box.keyPair();
|
||||
//console.log(keepass.b64e(keepass.keyPair.publicKey) + ' ' + keepass.b64e(keepass.keyPair.secretKey));
|
||||
//console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey));
|
||||
};
|
||||
|
||||
keepass.isConfigured = function(callback) {
|
||||
if (typeof(keepass.databaseHash) === 'undefined') {
|
||||
keepass.getDatabaseHash((dbHash) => {
|
||||
callback(keepass.databaseHash in keepass.keyRing);
|
||||
}, null);
|
||||
}
|
||||
else {
|
||||
callback(keepass.databaseHash in keepass.keyRing);
|
||||
}
|
||||
keepass.isConfigured = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof(keepass.databaseHash) === 'undefined') {
|
||||
keepass.getDatabaseHash((hash) => {
|
||||
resolve(hash in keepass.keyRing);
|
||||
});
|
||||
} else {
|
||||
resolve(keepass.databaseHash in keepass.keyRing);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
keepass.checkDatabaseHash = function(callback, tab) {
|
||||
callback(keepass.databaseHash);
|
||||
};
|
||||
|
||||
keepass.isAssociated = function() {
|
||||
return (keepass.associated.value && keepass.associated.hash && keepass.associated.hash === keepass.databaseHash);
|
||||
};
|
||||
|
||||
keepass.convertKeyToKeyRing = function() {
|
||||
if (keepass.keyId in localStorage && keepass.keyBody in localStorage && !('keyRing' in localStorage)) {
|
||||
keepass.getDatabaseHash((hash) => {
|
||||
keepass.saveKey(hash, localStorage[keepass.keyId], localStorage[keepass.keyBody]);
|
||||
|
||||
if ('keyRing' in localStorage) {
|
||||
delete localStorage[keepass.keyId];
|
||||
delete localStorage[keepass.keyBody];
|
||||
keepass.migrateKeyRing = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
browser.storage.local.get('keyRing').then((item) => {
|
||||
const keyring = item.keyRing;
|
||||
// Change dates to numbers, for compatibilty with Chromium based browsers
|
||||
if (keyring) {
|
||||
let num = 0;
|
||||
for (let keyHash in keyring) {
|
||||
let key = keyring[keyHash];
|
||||
['created', 'lastUsed'].forEach((fld) => {
|
||||
let v = key[fld];
|
||||
if (v instanceof Date && v.valueOf() >= 0) {
|
||||
key[fld] = v.valueOf();
|
||||
num++;
|
||||
} else if (typeof v !== 'number') {
|
||||
key[fld] = Date.now().valueOf();
|
||||
num++;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (num > 0) {
|
||||
browser.storage.local.set({ keyRing: keyring });
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
if ('keyRing' in localStorage) {
|
||||
delete localStorage[keepass.keyId];
|
||||
delete localStorage[keepass.keyBody];
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
keepass.saveKey = function(hash, id, key) {
|
||||
|
|
@ -660,8 +721,8 @@ keepass.saveKey = function(hash, id, key) {
|
|||
id: id,
|
||||
key: key,
|
||||
hash: hash,
|
||||
created: new Date(),
|
||||
lastUsed: new Date()
|
||||
created: new Date().valueOf(),
|
||||
lastUsed: new Date().valueOf()
|
||||
};
|
||||
}
|
||||
else {
|
||||
|
|
@ -669,19 +730,19 @@ keepass.saveKey = function(hash, id, key) {
|
|||
keepass.keyRing[hash].key = key;
|
||||
keepass.keyRing[hash].hash = hash;
|
||||
}
|
||||
localStorage.keyRing = JSON.stringify(keepass.keyRing);
|
||||
browser.storage.local.set({'keyRing': keepass.keyRing});
|
||||
};
|
||||
|
||||
keepass.updateLastUsed = function(hash) {
|
||||
if ((hash in keepass.keyRing)) {
|
||||
keepass.keyRing[hash].lastUsed = new Date();
|
||||
localStorage.keyRing = JSON.stringify(keepass.keyRing);
|
||||
keepass.keyRing[hash].lastUsed = new Date().valueOf();
|
||||
browser.storage.local.set({'keyRing': keepass.keyRing});
|
||||
}
|
||||
};
|
||||
|
||||
keepass.deleteKey = function(hash) {
|
||||
delete keepass.keyRing[hash];
|
||||
localStorage.keyRing = JSON.stringify(keepass.keyRing);
|
||||
browser.storage.local.set({'keyRing': keepass.keyRing});
|
||||
};
|
||||
|
||||
keepass.setcurrentKeePassXCVersion = function(version) {
|
||||
|
|
@ -695,7 +756,7 @@ keepass.setcurrentKeePassXCVersion = function(version) {
|
|||
|
||||
keepass.keePassXCUpdateAvailable = function() {
|
||||
if (page.settings.checkUpdateKeePassXC && page.settings.checkUpdateKeePassXC > 0) {
|
||||
const lastChecked = (keepass.latestKeePassXC.lastChecked) ? new Date(keepass.latestKeePassXC.lastChecked) : new Date('11/21/1986');
|
||||
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);
|
||||
if (daysSinceLastCheck >= page.settings.checkUpdateKeePassXC) {
|
||||
keepass.checkForNewKeePassXCVersion();
|
||||
|
|
@ -720,7 +781,7 @@ keepass.checkForNewKeePassXCVersion = function() {
|
|||
}
|
||||
|
||||
if (version !== -1) {
|
||||
localStorage.latestKeePassXC = JSON.stringify(keepass.latestKeePassXC);
|
||||
browser.storage.local.set({'latestKeePassXC': keepass.latestKeePassXC});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -735,13 +796,14 @@ keepass.checkForNewKeePassXCVersion = function() {
|
|||
catch (ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
keepass.latestKeePassXC.lastChecked = new Date();
|
||||
keepass.latestKeePassXC.lastChecked = new Date().valueOf();
|
||||
};
|
||||
|
||||
keepass.connectToNative = function() {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.nativeConnect();
|
||||
if (keepass.nativePort) {
|
||||
keepass.nativePort.disconnect();
|
||||
}
|
||||
keepass.nativeConnect();
|
||||
};
|
||||
|
||||
keepass.onNativeMessage = function(response) {
|
||||
|
|
@ -750,7 +812,7 @@ keepass.onNativeMessage = function(response) {
|
|||
// Handle database lock/unlock status
|
||||
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
|
||||
keepass.testAssociation((response) => {
|
||||
keepass.isConfigured((configured) => {
|
||||
keepass.isConfigured().then((configured) => {
|
||||
let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
|
||||
data.iconType = configured ? 'normal' : 'cross';
|
||||
browserAction.show(null, {'id': page.currentTabId});
|
||||
|
|
@ -769,6 +831,26 @@ function onDisconnected() {
|
|||
console.log('Failed to connect: ' + (browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message));
|
||||
}
|
||||
|
||||
keepass.getNonce = function() {
|
||||
return nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize));
|
||||
};
|
||||
|
||||
keepass.incrementedNonce = function(nonce) {
|
||||
const oldNonce = nacl.util.decodeBase64(nonce);
|
||||
let newNonce = oldNonce.slice(0);
|
||||
|
||||
// from libsodium/utils.c
|
||||
let i = 0;
|
||||
let c = 1;
|
||||
for (; i < newNonce.length; ++i) {
|
||||
c += newNonce[i];
|
||||
newNonce[i] = c;
|
||||
c >>= 8;
|
||||
}
|
||||
|
||||
return nacl.util.encodeBase64(newNonce);
|
||||
};
|
||||
|
||||
keepass.nativeConnect = function() {
|
||||
console.log('Connecting to native messaging host ' + keepass.nativeHostName);
|
||||
keepass.nativePort = browser.runtime.connectNative(keepass.nativeHostName);
|
||||
|
|
@ -784,13 +866,14 @@ keepass.verifyKeyResponse = function(response, key, nonce) {
|
|||
}
|
||||
|
||||
let reply = false;
|
||||
if (keepass.b64d(nonce).length !== nacl.secretbox.nonceLength)
|
||||
if (nacl.util.decodeBase64(nonce).length !== nacl.secretbox.nonceLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
reply = (response.nonce === nonce);
|
||||
|
||||
if (response.publicKey) {
|
||||
keepass.serverPublicKey = keepass.b64d(response.publicKey);
|
||||
keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey);
|
||||
reply = true;
|
||||
}
|
||||
|
||||
|
|
@ -806,10 +889,14 @@ keepass.verifyResponse = function(response, nonce, id) {
|
|||
|
||||
keepass.associated.hash = keepass.databaseHash;
|
||||
|
||||
if (keepass.b64d(response.nonce).length !== nacl.secretbox.nonceLength)
|
||||
if (nacl.util.decodeBase64(response.nonce).length !== nacl.secretbox.nonceLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.value = (response.nonce === nonce);
|
||||
if (keepass.associated.value === false) {
|
||||
console.log("Compare failed");
|
||||
}
|
||||
|
||||
if (id) {
|
||||
keepass.associated.value = (keepass.associated.value && id === response.id);
|
||||
|
|
@ -830,14 +917,6 @@ keepass.handleError = function(tab, errorCode, errorMessage = '') {
|
|||
}
|
||||
};
|
||||
|
||||
keepass.b64e = function(d) {
|
||||
return nacl.util.encodeBase64(d);
|
||||
};
|
||||
|
||||
keepass.b64d = function(d) {
|
||||
return nacl.util.decodeBase64(d);
|
||||
};
|
||||
|
||||
keepass.getCryptoKey = function() {
|
||||
let dbkey = null;
|
||||
let dbid = null;
|
||||
|
|
@ -860,19 +939,20 @@ keepass.setCryptoKey = function(id, key) {
|
|||
|
||||
keepass.encrypt = function(input, nonce) {
|
||||
const messageData = nacl.util.decodeUTF8(JSON.stringify(input));
|
||||
const messageNonce = nacl.util.decodeBase64(nonce);
|
||||
|
||||
if (keepass.serverPublicKey) {
|
||||
const message = nacl.box(messageData, nonce, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
const message = nacl.box(messageData, messageNonce, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
if (message) {
|
||||
return keepass.b64e(message);
|
||||
return nacl.util.encodeBase64(message);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
keepass.decrypt = function(input, nonce, toStr) {
|
||||
const m = keepass.b64d(input);
|
||||
const n = keepass.b64d(nonce);
|
||||
keepass.decrypt = function(input, nonce) {
|
||||
const m = nacl.util.decodeBase64(input);
|
||||
const n = nacl.util.decodeBase64(nonce);
|
||||
const res = nacl.box.open(m, n, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
return res;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,56 +4,59 @@ const defaultSettings = {
|
|||
autoFillAndSend: true,
|
||||
usePasswordGenerator: true,
|
||||
autoFillSingleEntry: false,
|
||||
autoRetrieveCredentials: true,
|
||||
proxyPort: '19700'
|
||||
autoRetrieveCredentials: true
|
||||
};
|
||||
|
||||
var page = {};
|
||||
page.tabs = {};
|
||||
|
||||
page.currentTabId = -1;
|
||||
page.settings = (typeof(localStorage.settings) === 'undefined') ? {} : JSON.parse(localStorage.settings);
|
||||
page.blockedTabs = {};
|
||||
|
||||
page.initSettings = function() {
|
||||
kpxcEvent.onLoadSettings();
|
||||
if (!('checkUpdateKeePassXC' in page.settings)) {
|
||||
page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC;
|
||||
}
|
||||
if (!('autoCompleteUsernames' in page.settings)) {
|
||||
page.settings.autoCompleteUsernames = defaultSettings.autoCompleteUsernames;
|
||||
}
|
||||
if (!('autoFillAndSend' in page.settings)) {
|
||||
page.settings.autoFillAndSend = defaultSettings.autoFillAndSend;
|
||||
}
|
||||
if (!('usePasswordGenerator' in page.settings)) {
|
||||
page.settings.usePasswordGenerator = defaultSettings.usePasswordGenerator;
|
||||
}
|
||||
if (!('autoFillSingleEntry' in page.settings)) {
|
||||
page.settings.autoFillSingleEntry = defaultSettings.autoFillSingleEntry;
|
||||
}
|
||||
if (!('autoRetrieveCredentials' in page.settings)) {
|
||||
page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials;
|
||||
}
|
||||
if (!('port' in page.settings)) {
|
||||
page.settings.port = defaultSettings.proxyPort;
|
||||
}
|
||||
localStorage.settings = JSON.stringify(page.settings);
|
||||
return new Promise((resolve, reject) => {
|
||||
kpxcEvent.onLoadSettings((settings) => {
|
||||
page.settings = settings;
|
||||
if (!('checkUpdateKeePassXC' in page.settings)) {
|
||||
page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC;
|
||||
}
|
||||
if (!('autoCompleteUsernames' in page.settings)) {
|
||||
page.settings.autoCompleteUsernames = defaultSettings.autoCompleteUsernames;
|
||||
}
|
||||
if (!('autoFillAndSend' in page.settings)) {
|
||||
page.settings.autoFillAndSend = defaultSettings.autoFillAndSend;
|
||||
}
|
||||
if (!('usePasswordGenerator' in page.settings)) {
|
||||
page.settings.usePasswordGenerator = defaultSettings.usePasswordGenerator;
|
||||
}
|
||||
if (!('autoFillSingleEntry' in page.settings)) {
|
||||
page.settings.autoFillSingleEntry = defaultSettings.autoFillSingleEntry;
|
||||
}
|
||||
if (!('autoRetrieveCredentials' in page.settings)) {
|
||||
page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials;
|
||||
}
|
||||
browser.storage.local.set({'settings': page.settings});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
page.initOpenedTabs = function() {
|
||||
browser.tabs.query({}).then((tabs) => {
|
||||
for (const i of tabs) {
|
||||
page.createTabEntry(i.id);
|
||||
}
|
||||
|
||||
// set initial tab-ID
|
||||
browser.tabs.query({ "active": true, "currentWindow": true }).then((tabs) => {
|
||||
if (tabs.length === 0) {
|
||||
return; // For example: only the background devtools or a popup are opened
|
||||
return new Promise((resolve, reject) => {
|
||||
browser.tabs.query({}).then((tabs) => {
|
||||
for (const i of tabs) {
|
||||
page.createTabEntry(i.id);
|
||||
}
|
||||
page.currentTabId = tabs[0].id;
|
||||
browserAction.show(null, tabs[0]);
|
||||
|
||||
// set initial tab-ID
|
||||
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
|
||||
if (tabs.length === 0) {
|
||||
resolve();
|
||||
return; // For example: only the background devtools or a popup are opened
|
||||
}
|
||||
page.currentTabId = tabs[0].id;
|
||||
browserAction.show(null, tabs[0]);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -66,7 +69,7 @@ page.isValidProtocol = function(url) {
|
|||
|
||||
page.switchTab = function(callback, tab) {
|
||||
browserAction.showDefault(null, tab);
|
||||
browser.tabs.sendMessage(tab.id, {action: 'activated_tab'}).catch((e) => {console.log(e);});
|
||||
browser.tabs.sendMessage(tab.id, {action: 'activated_tab'}).catch((e) => {});
|
||||
};
|
||||
|
||||
page.clearCredentials = function(tabId, complete) {
|
||||
|
|
@ -82,7 +85,7 @@ page.clearCredentials = function(tabId, complete) {
|
|||
|
||||
browser.tabs.sendMessage(tabId, {
|
||||
action: 'clear_credentials'
|
||||
}).catch((e) => {console.log(e);});
|
||||
}).catch((e) => {});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -101,7 +104,7 @@ page.createTabEntry = function(tabId) {
|
|||
page.removePageInformationFromNotExistingTabs = function() {
|
||||
let rand = Math.floor(Math.random()*1001);
|
||||
if (rand === 28) {
|
||||
browser.tabs.query({}, (tabs) => {
|
||||
browser.tabs.query({}).then(function(tabs) {
|
||||
let $tabIds = {};
|
||||
const $infoIds = Object.keys(page.tabs);
|
||||
|
||||
|
|
|
|||
8
keepassxc-browser/jquery-ui.min.css
vendored
8
keepassxc-browser/jquery-ui.min.css
vendored
File diff suppressed because one or more lines are too long
20
keepassxc-browser/jquery-ui.min.js
vendored
20
keepassxc-browser/jquery-ui.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,38 +1,47 @@
|
|||
.cip-ui-autocomplete li.cip-ui-menu-item {
|
||||
.kpxc .ui-autocomplete li.ui-menu-item {
|
||||
text-align: left !important;
|
||||
font-size: 12px !important;
|
||||
font-size: .9em !important;
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-family: Verdana, Arial, sans-serif !important;
|
||||
color: #222222 !important;
|
||||
}
|
||||
|
||||
.ui-dialog-titlebar-close {
|
||||
.ui-helper-hidden-accessible {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.kpxc .ui-dialog-titlebar-close {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.ui-dialog {
|
||||
.kpxc .ui-dialog {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.dialog-form .ui-dialog-content .ui-widget-content {
|
||||
.kpxc .ui-widget-overlay {
|
||||
opacity: 0.0 !important;
|
||||
}
|
||||
|
||||
.kpxc .dialog-form .ui-dialog-content .ui-widget-content {
|
||||
max-height: 80px !important;
|
||||
}
|
||||
|
||||
.ui-dialog-titlebar {
|
||||
.kpxc .ui-dialog-titlebar {
|
||||
background-color: #3a8233;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
.kpxc .ui-dialog .ui-dialog-buttonpane {
|
||||
text-align: center !important;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
||||
|
||||
.kpxc .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.ui-button .ui-button-text .ui-button {
|
||||
font-size: .10em !important;
|
||||
.kpxc .ui-button .ui-button-text .ui-button {
|
||||
font-size: .10em !important;
|
||||
}
|
||||
|
||||
input.genpw-text {
|
||||
|
|
@ -44,6 +53,7 @@ input.genpw-text {
|
|||
border-collapse: separate;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.genpw-input-group-addon {
|
||||
font-size: inherit !important;
|
||||
background-color: #eee;
|
||||
|
|
@ -69,10 +79,12 @@ input.genpw-text {
|
|||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cip-genpw-icon.key {
|
||||
background: url('chrome-extension://__MSG_@@extension_id__/icons/key.png') right no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.cip-genpw-icon.key-moz {
|
||||
background: url('moz-extension://__MSG_@@extension_id__/icons/key.png') right no-repeat;
|
||||
background-size: contain;
|
||||
|
|
@ -90,6 +102,7 @@ input.genpw-text {
|
|||
left: 0;
|
||||
z-index: 2147483645;
|
||||
}
|
||||
|
||||
.b2c-modal-backdrop:after {
|
||||
content:'';
|
||||
position: fixed;
|
||||
|
|
@ -101,9 +114,11 @@ input.genpw-text {
|
|||
opacity: 0.8;
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
|
||||
#b2c-cipDefine-fields {
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
#b2c-cipDefine-description {
|
||||
z-index: 2147483646;
|
||||
color: #efefef;
|
||||
|
|
@ -117,6 +132,7 @@ input.genpw-text {
|
|||
background-color:rgba(255,255,255,0.3);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#b2c-cipDefine-description div:first-of-type {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
|
|
@ -126,6 +142,7 @@ input.genpw-text {
|
|||
font-weight: bold;
|
||||
font-size: 160%;
|
||||
}
|
||||
|
||||
#b2c-cipDefine-description p {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
|
|
@ -133,9 +150,11 @@ input.genpw-text {
|
|||
border-top: 2px solid #666666;
|
||||
line-height: 110%;
|
||||
}
|
||||
|
||||
#b2c-help {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.b2c-fixed-field {
|
||||
position: absolute;
|
||||
border: 2px solid #efefef;
|
||||
|
|
@ -145,27 +164,30 @@ input.genpw-text {
|
|||
font-weight: bold;
|
||||
background-color:rgba(239,239,239,0.4);
|
||||
}
|
||||
|
||||
.b2c-fixed-hover-field {
|
||||
border: 2px solid orange;
|
||||
background-color:rgba(255,165,239,0.4);
|
||||
}
|
||||
|
||||
.b2c-fixed-password-field {
|
||||
border: 2px solid red;
|
||||
color: #efefef;
|
||||
background-color:rgba(255,0,0,0.4);
|
||||
}
|
||||
|
||||
.b2c-fixed-username-field {
|
||||
border: 2px solid limegreen;
|
||||
color: #efefef;
|
||||
background-color:rgba(50,205,50,0.4);
|
||||
}
|
||||
|
||||
.b2c-fixed-string-field {
|
||||
border: 2px solid deepskyblue;
|
||||
color: #efefef;
|
||||
background-color:rgba(30,144,255,0.4);
|
||||
}
|
||||
|
||||
|
||||
.b2c-input-append {
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
|
|
@ -197,4 +219,4 @@ input.genpw-text {
|
|||
.b2c-input-append select:focus,
|
||||
.b2c-input-append .b2c-b2c-uneditable-input:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ $(this.target).find('input').autocomplete();
|
|||
// contains already called method names
|
||||
var _called = {};
|
||||
|
||||
// Count of detected form fields on the page
|
||||
var _detectedFields = 0;
|
||||
|
||||
browser.runtime.onMessage.addListener(function(req, sender, callback) {
|
||||
if ('action' in req) {
|
||||
if (req.action === 'fill_user_pass_with_specific_login') {
|
||||
|
|
@ -46,13 +49,15 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
|
|||
}
|
||||
else if (req.action === 'clear_credentials') {
|
||||
cipEvents.clearCredentials();
|
||||
callback();
|
||||
}
|
||||
else if (req.action === 'activated_tab') {
|
||||
cipEvents.triggerActivatedTab();
|
||||
callback();
|
||||
}
|
||||
else if (req.action === 'redetect_fields') {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'get_settings',
|
||||
action: 'load_settings',
|
||||
}).then((response) => {
|
||||
cip.settings = response.data;
|
||||
cip.initCredentialFields(true);
|
||||
|
|
@ -78,13 +83,19 @@ var cipAutocomplete = {};
|
|||
cipAutocomplete.elements = [];
|
||||
|
||||
cipAutocomplete.init = function(field) {
|
||||
if (field.hasClass('ui-autocomplete-input')) {
|
||||
//_f(credentialInputs[i].username).autocomplete('source', autocompleteSource);
|
||||
if (cip.settings.autoFillSingleEntry && cip.credentials.length === 1 && field.hasClass('ui-autocomplete-input')) {
|
||||
field.autocomplete('destroy');
|
||||
}
|
||||
|
||||
let acMenu = jQuery('#kpxc-ac-menu');
|
||||
if (acMenu.length == 0) {
|
||||
jQuery('<div id="kpxc-ac-menu" class="kpxc"></div>').appendTo('body');
|
||||
}
|
||||
|
||||
field
|
||||
.addClass('kpxc')
|
||||
.autocomplete({
|
||||
appendTo: '#kpxc-ac-menu',
|
||||
minLength: 0,
|
||||
source: cipAutocomplete.onSource,
|
||||
select: cipAutocomplete.onSelect,
|
||||
|
|
@ -101,12 +112,10 @@ cipAutocomplete.onClick = function() {
|
|||
};
|
||||
|
||||
cipAutocomplete.onOpen = function(event, ui) {
|
||||
// NOT BEAUTIFUL!
|
||||
// modifies ALL ui-autocomplete menus of class .cip-ui-menu
|
||||
jQuery('ul.ui-autocomplete.ui-menu').css('z-index', 2147483636);
|
||||
};
|
||||
|
||||
cipAutocomplete.onSource = function (request, callback) {
|
||||
cipAutocomplete.onSource = function(request, callback) {
|
||||
const matches = jQuery.map(cipAutocomplete.elements, (tag) => {
|
||||
if (tag.label.toUpperCase().indexOf(request.term.toUpperCase()) === 0) {
|
||||
return tag;
|
||||
|
|
@ -115,7 +124,7 @@ cipAutocomplete.onSource = function (request, callback) {
|
|||
callback(matches);
|
||||
};
|
||||
|
||||
cipAutocomplete.onSelect = function (e, ui) {
|
||||
cipAutocomplete.onSelect = function(e, ui) {
|
||||
e.preventDefault();
|
||||
cip.setValueWithChange(jQuery(this), ui.item.value);
|
||||
const fieldId = cipFields.prepareId(jQuery(this).attr('data-cip-id'));
|
||||
|
|
@ -237,6 +246,7 @@ cipPassword.createDialog = function() {
|
|||
|
||||
$dialog.hide();
|
||||
jQuery('body').append($dialog);
|
||||
|
||||
$dialog.dialog({
|
||||
autoOpen: false,
|
||||
modal: true,
|
||||
|
|
@ -254,7 +264,7 @@ cipPassword.createDialog = function() {
|
|||
e.preventDefault();
|
||||
browser.runtime.sendMessage({
|
||||
action: 'generate_password'
|
||||
}).then(cipPassword.callbackGeneratedPassword);
|
||||
}).then(cipPassword.callbackGeneratedPassword).catch((e) => {console.log(e);});
|
||||
}
|
||||
},
|
||||
'Copy':
|
||||
|
|
@ -304,14 +314,20 @@ cipPassword.createDialog = function() {
|
|||
}
|
||||
},
|
||||
open: function(event, ui) {
|
||||
// Dirty hacks for overlay and custom CSS
|
||||
jQuery('.ui-widget-overlay').wrap('<span class="kpxc"></span>');
|
||||
jQuery('.ui-widget-overlay').click(function() {
|
||||
jQuery('#cip-genpw-dialog:first').dialog('close');
|
||||
jQuery('span').remove('.kpxc');
|
||||
});
|
||||
|
||||
if (jQuery('input#cip-genpw-textfield-password:first').val() === '') {
|
||||
jQuery('button#cip-genpw-btn-generate:first').click();
|
||||
}
|
||||
}
|
||||
},
|
||||
create: function(event, ui) {
|
||||
jQuery('.ui-dialog').wrap('<div class="kpxc"></span>');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -394,7 +410,6 @@ cipPassword.callbackPasswordCopied = function(bool) {
|
|||
|
||||
cipPassword.callbackGeneratedPassword = function(entries) {
|
||||
if (entries && entries.length >= 1) {
|
||||
console.log(entries[0]);
|
||||
jQuery('#cip-genpw-btn-clipboard:first').removeClass('btn-success');
|
||||
jQuery('input#cip-genpw-textfield-password:first').val(entries[0].password);
|
||||
if (isNaN(entries[0].login)) {
|
||||
|
|
@ -406,7 +421,7 @@ cipPassword.callbackGeneratedPassword = function(entries) {
|
|||
}
|
||||
else {
|
||||
if (jQuery('div#cip-genpw-error:first').length === 0) {
|
||||
jQuery('button#cip-genpw-btn-generate:first').after('<div style=\'block\' id=\'cip-genpw-error\'>Cannot receive generated password.<br />Is your version of KeePassXC up-to-date?<br /><br /><a href=\'https://keepassxc.org\'>Please visit the KeePassXC homepage</a></div>');
|
||||
jQuery('button#cip-genpw-btn-generate:first').after('<div style=\'block\' id=\'cip-genpw-error\'>Cannot receive generated password.<br />Is KeePassXC opened?<br /></div>');
|
||||
jQuery('input#cip-genpw-textfield-password:first').parent().hide();
|
||||
jQuery('input#cip-genpw-checkbox-next-field:first').parent('label').hide();
|
||||
jQuery('button#cip-genpw-btn-generate').hide();
|
||||
|
|
@ -805,6 +820,7 @@ cipFields.getAllFields = function() {
|
|||
}
|
||||
});
|
||||
|
||||
_detectedFields = fields.length;
|
||||
return fields;
|
||||
};
|
||||
|
||||
|
|
@ -1074,7 +1090,7 @@ cipFields.useDefinedCredentialFields = function() {
|
|||
}
|
||||
|
||||
if ($found) {
|
||||
var fields = {
|
||||
let fields = {
|
||||
username: creds.username,
|
||||
password: creds.password,
|
||||
fields: creds.fields
|
||||
|
|
@ -1107,31 +1123,30 @@ jQuery(function() {
|
|||
|
||||
cip.init = function() {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'get_settings',
|
||||
action: 'load_settings',
|
||||
}).then((response) => {
|
||||
cip.settings = response.data;
|
||||
cip.initCredentialFields();
|
||||
cip.settings = response;
|
||||
cip.initCredentialFields(true);
|
||||
});
|
||||
};
|
||||
|
||||
cip.detectNewActiveFields = function() {
|
||||
const hiddenFields = cipFields.getHiddenFieldCount();
|
||||
|
||||
// If hidden fields aren't detected, setInterval is being looped in each frame of the page
|
||||
//if (hiddenFields > 0) {
|
||||
const divDetect = setInterval(function() {
|
||||
const divDetect = setInterval(function() {
|
||||
if (document.visibilityState !== 'hidden') {
|
||||
const fields = cipFields.getAllFields();
|
||||
if (fields.length > 1) {
|
||||
|
||||
// If only password field is shown it's enough to have one field visible for initCredentialFields
|
||||
if (fields.length > (_detectedFields == 1 ? 0 : 1)) {
|
||||
cip.initCredentialFields(true);
|
||||
clearInterval(divDetect);
|
||||
}
|
||||
}, 1000);
|
||||
//}
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// Switch credentials if database is changed or closed
|
||||
cip.detectDatabaseChange = function() {
|
||||
const dbDetectInterval = setInterval(function() {
|
||||
let dbDetectInterval = setInterval(function() {
|
||||
if (document.visibilityState !== 'hidden') {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'check_databasehash'
|
||||
|
|
@ -1145,21 +1160,22 @@ cip.detectDatabaseChange = function() {
|
|||
|
||||
// Switch back to default popup
|
||||
browser.runtime.sendMessage({
|
||||
action: 'get_status'
|
||||
action: 'get_status',
|
||||
args: [ true ] // Set polling to true, this is an internal function call
|
||||
});
|
||||
} else {
|
||||
if (response.new !== 'no-hash' && response.new !== response.old) {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'get_settings',
|
||||
action: 'load_settings',
|
||||
}).then((response) => {
|
||||
cip.settings = response.data;
|
||||
cip.settings = response;
|
||||
cip.initCredentialFields(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}).catch((e) => {console.log(e);});
|
||||
}
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
cip.initCredentialFields = function(forceCall) {
|
||||
|
|
@ -1193,7 +1209,7 @@ cip.initCredentialFields = function(forceCall) {
|
|||
browser.runtime.sendMessage({
|
||||
action: 'retrieve_credentials',
|
||||
args: [ cip.url, cip.submitUrl ]
|
||||
}).then(cip.retrieveCredentialsCallback);
|
||||
}).then(cip.retrieveCredentialsCallback).catch((e) => {console.log(e);});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1293,8 +1309,16 @@ cip.preparePageForMultipleCredentials = function(credentials) {
|
|||
// initialize autocomplete for username fields
|
||||
if (cip.settings.autoCompleteUsernames) {
|
||||
for (const i of cipFields.combinations) {
|
||||
if (_f(i.username)) {
|
||||
cipAutocomplete.init(_f(i.username));
|
||||
// Both username and password fields are visible
|
||||
if (_detectedFields >= 2) {
|
||||
if (_f(i.username)) {
|
||||
cipAutocomplete.init(_f(i.username));
|
||||
}
|
||||
} else if (_detectedFields == 1) {
|
||||
// If only password field is the visible one
|
||||
if (_f(i.password)) {
|
||||
cipAutocomplete.init(_f(i.password));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1747,6 +1771,6 @@ cipEvents.triggerActivatedTab = function() {
|
|||
browser.runtime.sendMessage({
|
||||
action: 'retrieve_credentials',
|
||||
args: [ cip.url, cip.submitUrl ]
|
||||
}).then(cip.retrieveCredentialsCallback);
|
||||
}).then(cip.retrieveCredentialsCallback).catch((e) => {console.log(e);});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "keepassxc-browser",
|
||||
"version": "0.3.6",
|
||||
"version": "0.4.2",
|
||||
"description": "KeePassXC integration for modern web browsers",
|
||||
"author": "Sami Vänttinen",
|
||||
"icons": {
|
||||
|
|
@ -41,6 +41,9 @@
|
|||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"exclude_matches": [
|
||||
"*://*/*.xml"
|
||||
],
|
||||
"js": [
|
||||
"browser-polyfill.min.js",
|
||||
"global.js",
|
||||
|
|
@ -67,8 +70,8 @@
|
|||
"fill-password": {
|
||||
"description": "Insert a password",
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+Shift+P",
|
||||
"mac": "Command+Shift+P"
|
||||
"default": "Alt+Shift+P",
|
||||
"mac": "Alt+Shift+P"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -84,24 +84,6 @@
|
|||
</span>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<div class="form-group">
|
||||
<label for="port">UDP port for proxy applications:</label>
|
||||
<div class="control-group">
|
||||
<div class="input-append">
|
||||
<input type="number" id="port" placeholder="19700" value="19700" />
|
||||
<button class="btn btn-sm btn-primary" id="portButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> Save</button>
|
||||
</div>
|
||||
<span class="help-inline">
|
||||
Change the port if you have trouble with running KeePassXC on the default port.
|
||||
<br />
|
||||
You have to set the same port number in KeePassXC options.
|
||||
<br />
|
||||
Default: 19700
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
<div class="checkbox">
|
||||
|
|
|
|||
|
|
@ -3,18 +3,21 @@ if (jQuery) {
|
|||
}
|
||||
|
||||
$(function() {
|
||||
options.initMenu();
|
||||
options.initGeneralSettings();
|
||||
options.initConnectedDatabases();
|
||||
options.initSpecifiedCredentialFields();
|
||||
options.initAbout();
|
||||
browser.runtime.sendMessage({ action: 'load_settings' }).then((settings) => {
|
||||
options.settings = settings;
|
||||
browser.runtime.sendMessage({ action: 'load_keyring' }).then((keyRing) => {
|
||||
options.keyRing = keyRing;
|
||||
options.initMenu();
|
||||
options.initGeneralSettings();
|
||||
options.initConnectedDatabases();
|
||||
options.initSpecifiedCredentialFields();
|
||||
options.initAbout();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var options = options || {};
|
||||
|
||||
options.settings = typeof(localStorage.settings) === 'undefined' ? {} : JSON.parse(localStorage.settings);
|
||||
options.keyRing = typeof(localStorage.keyRing) === 'undefined' ? {} : JSON.parse(localStorage.keyRing);
|
||||
|
||||
options.initMenu = function() {
|
||||
$('.navbar:first ul.nav:first li a').click(function(e) {
|
||||
e.preventDefault();
|
||||
|
|
@ -32,22 +35,21 @@ options.saveSetting = function(name) {
|
|||
$($id).closest('.control-group').removeClass('error').addClass('success');
|
||||
setTimeout(() => { $($id).closest('.control-group').removeClass('success'); }, 2500);
|
||||
|
||||
localStorage.settings = JSON.stringify(options.settings);
|
||||
|
||||
browser.storage.local.set({'settings': options.settings});
|
||||
browser.runtime.sendMessage({
|
||||
action: 'load_settings'
|
||||
});
|
||||
};
|
||||
|
||||
options.saveSettings = function() {
|
||||
localStorage.settings = JSON.stringify(options.settings);
|
||||
browser.storage.local.set({'settings': options.settings});
|
||||
browser.runtime.sendMessage({
|
||||
action: 'load_settings'
|
||||
});
|
||||
};
|
||||
|
||||
options.saveKeyRing = function() {
|
||||
localStorage.keyRing = JSON.stringify(options.keyRing);
|
||||
browser.storage.local.set({'keyRing': options.keyRing});
|
||||
browser.runtime.sendMessage({
|
||||
action: 'load_keyring'
|
||||
});
|
||||
|
|
@ -86,28 +88,14 @@ options.initGeneralSettings = function() {
|
|||
}).then(options.showKeePassXCVersions);
|
||||
});
|
||||
|
||||
$('#port').val(options.settings['port']);
|
||||
$('#blinkTimeout').val(options.settings['blinkTimeout']);
|
||||
$('#blinkMinTimeout').val(options.settings['blinkMinTimeout']);
|
||||
$('#allowedRedirect').val(options.settings['allowedRedirect']);
|
||||
|
||||
$('#portButton').click(function() {
|
||||
const port = $.trim($('#port').val());
|
||||
const portNumber = Number(port);
|
||||
if (isNaN(port) || portNumber < 1025 || portNumber > 99999) {
|
||||
$('#port').closest('.control-group').addClass('error');
|
||||
alert('The port number has to be in range 1025 - 99999.\nNothing saved!');
|
||||
return;
|
||||
}
|
||||
|
||||
options.settings['port'] = String(portNumber);
|
||||
options.saveSetting('port');
|
||||
});
|
||||
|
||||
$('#blinkTimeoutButton').click(function(){
|
||||
const blinkTimeout = $.trim($('#blinkTimeout').val());
|
||||
const blinkTimeoutval = Number(blinkTimeout);
|
||||
|
||||
|
||||
options.settings['blinkTimeout'] = String(blinkTimeoutval);
|
||||
options.saveSetting('blinkTimeout');
|
||||
});
|
||||
|
|
@ -115,7 +103,7 @@ options.initGeneralSettings = function() {
|
|||
$('#blinkMinTimeoutButton').click(function(){
|
||||
const blinkMinTimeout = $.trim($('#blinkMinTimeout').val());
|
||||
const blinkMinTimeoutval = Number(blinkMinTimeout);
|
||||
|
||||
|
||||
options.settings['blinkMinTimeout'] = String(blinkMinTimeoutval);
|
||||
options.saveSetting('blinkMinTimeout');
|
||||
});
|
||||
|
|
@ -123,7 +111,7 @@ options.initGeneralSettings = function() {
|
|||
$('#allowedRedirectButton').click(function(){
|
||||
const allowedRedirect = $.trim($('#allowedRedirect').val());
|
||||
const allowedRedirectval = Number(allowedRedirect);
|
||||
|
||||
|
||||
options.settings['allowedRedirect'] = String(allowedRedirectval);
|
||||
options.saveSetting('allowedRedirect');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
$(function() {
|
||||
browser.runtime.getBackgroundPage().then((global) => {
|
||||
browser.tabs.query({"active": true, "currentWindow": true}).then((tab) => {
|
||||
browser.tabs.query({'active': true, 'currentWindow': true}).then((tabs) => {
|
||||
let tab = tabs[0];
|
||||
const data = global.page.tabs[tab.id].loginList;
|
||||
let ul = document.getElementById('login-list');
|
||||
for (let i = 0; i < data.logins.length; i++) {
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.textContent = data.logins[i].login + ' (' + data.logins[i].name + ')';
|
||||
li.setAttribute('class', 'list-group-item');
|
||||
a.textContent = data.logins[i].login + " (" + data.logins[i].name + ")";
|
||||
li.appendChild(a);
|
||||
$(a).data('url', data.url.replace(/:\/\//g, '://' + data.logins[i].login + ':' + data.logins[i].password + '@'));
|
||||
$(a).click(() => {
|
||||
browser.tabs.update(tab.id, {'url': $(this).data('url')});
|
||||
$(a).data('creds', data.logins[i]);
|
||||
$(a).click(function () {
|
||||
if (data.resolve) {
|
||||
const creds = $(this).data('creds');
|
||||
data.resolve({
|
||||
authCredentials: {
|
||||
username: creds.login,
|
||||
password: creds.password
|
||||
}
|
||||
});
|
||||
}
|
||||
close();
|
||||
});
|
||||
ul.appendChild(li);
|
||||
|
|
|
|||
252
keepassxc-protocol.md
Normal file
252
keepassxc-protocol.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
## keepassxc-protocol
|
||||
|
||||
Transmitting messages between KeePassXC and keepassxc-browser is totally rewritten. This is still under development.
|
||||
Now the requests are encrypted by [TweetNaCl.js](https://github.com/dchest/tweetnacl-js) box method and does the following:
|
||||
|
||||
1. keepassxc-browser generates a key pair (with public and secret key) and transfers the public key to KeePassXC
|
||||
2. When KeePassXC receives the public key it generates its own key pair and transfers the public key to keepassxc-browser
|
||||
3. All messages between the browser extension and KeePassXC are now encrypted.
|
||||
4. When keepassxc-browser sends a message it is encrypted with KeePassXC's public key, a random generated nonce and keepassxc-browser's secret key.
|
||||
5. When KeePassXC sends a message it is encrypted with keepassxc-browser's public key and an incremented nonce.
|
||||
6. Databases are stored based on the current public key used with `associate`. A new key pair for data transfer is generated each time keepassxc-browser is launched.
|
||||
|
||||
Encrypted messages are built with these JSON parameters:
|
||||
- action - `test-associate`, `associate`, `get-logins`, `get-logins-count`, `set-login`...
|
||||
- message - Encrypted message, base64 encoded
|
||||
- nonce - 24 bytes long random data, base64 encoded. This must be the same when responding to a request.
|
||||
- clientID - 24 bytes long random data, base64 encoded. This is used to identify different browsers if multiple are used with proxy application.
|
||||
|
||||
Currently these messages are implemented:
|
||||
- `change-public-keys`: Request for passing public keys from client to server and back.
|
||||
- `get-databasehash`: Request for receiving the database hash (SHA256) of the current active database.
|
||||
- `associate`: Request for associating a new client with KeePassXC.
|
||||
- `test-associate`: Request for testing if the client has been associated with KeePassXC.
|
||||
- `generate-password`: Request for generating a password. KeePassXC's settings are used.
|
||||
- `get-logins`: Requests for receiving credentials for the current URL match.
|
||||
- `set-login`: Request for adding or updating credentials to the database.
|
||||
- `lock-database`: Request for locking the database from client.
|
||||
- `database-locked`: A signal from KeePassXC, the current active database is locked.
|
||||
- `database-unlocked`: A signal from KeePassXC, the current active database is unlocked.
|
||||
|
||||
### change-public-keys
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "change-public-keys",
|
||||
"publicKey": "<current public key>",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response (success):
|
||||
```javascript
|
||||
{
|
||||
"action": "change-public-keys",
|
||||
"version": "2.2.0",
|
||||
"publicKey": "<host public key>",
|
||||
"success": "true"
|
||||
}
|
||||
```
|
||||
|
||||
### get-databasehash
|
||||
Request (unencrypted):
|
||||
```javascript
|
||||
{
|
||||
"action": "get-databasehash"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"action": "hash",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
```
|
||||
|
||||
### associate
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "associate",
|
||||
"key": "<current public key>"
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "associate",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0",
|
||||
"success": "true",
|
||||
"id": "testclient",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
|
||||
### test-associate
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "test-associate",
|
||||
"id": "<saved database identifier>",
|
||||
"key": "<saved database public key>",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "test-associate",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"id": "testclient",
|
||||
"success": "true"
|
||||
}
|
||||
```
|
||||
|
||||
### generate-password
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "generate-password",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"entries": [
|
||||
{
|
||||
"login": 144,
|
||||
"password": "testclientpassword"
|
||||
}
|
||||
],
|
||||
"success": "true",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
|
||||
### get-logins
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "get-logins",
|
||||
"url": "<snip>",
|
||||
"submitUrl": optional
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "get-logins",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"count": "2",
|
||||
"entries" : [
|
||||
{
|
||||
"login": "user1",
|
||||
"name": "user1",
|
||||
"password": "passwd1"
|
||||
},
|
||||
{
|
||||
"login": "user2",
|
||||
"name": "user2",
|
||||
"password": "passwd2"
|
||||
}],
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"success": "true",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
```
|
||||
|
||||
### set-login
|
||||
Unencrypted message:
|
||||
```javascript
|
||||
{
|
||||
"action": "set-login",
|
||||
"url": "<snip>",
|
||||
"submitUrl": "<snip>",
|
||||
"id": "testclient",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"login": "user1",
|
||||
"password": "passwd1"
|
||||
}
|
||||
```
|
||||
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "set-login",
|
||||
"message": encryptedMessage
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"count": null,
|
||||
"entries" : null,
|
||||
"error": "",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"success": "true",
|
||||
"hash": "29234e32274a32276e25666a42",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
```
|
||||
|
||||
### lock-database
|
||||
Request:
|
||||
```javascript
|
||||
{
|
||||
"action": "lock-database",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q",
|
||||
"clientID": "<clientID>"
|
||||
}
|
||||
```
|
||||
|
||||
Response message data (success always returns an error, decrypted):
|
||||
```javascript
|
||||
{
|
||||
"action": "lock-database",
|
||||
"errorCode": 1,
|
||||
"error": "Database not opened",
|
||||
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
|
||||
}
|
||||
```
|
||||
Loading…
Reference in a new issue