browser extension backend 101

This commit is contained in:
antelle 2021-04-06 20:10:42 +02:00
parent 43f6a4f499
commit 4b17ac19dc
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
7 changed files with 181 additions and 6 deletions

View File

@ -6,6 +6,7 @@ import { ExportApi } from 'comp/app/export-api';
import { SingleInstanceChecker } from 'comp/app/single-instance-checker';
import { Updater } from 'comp/app/updater';
import { UsbListener } from 'comp/app/usb-listener';
import { BrowserExtensionConnector } from 'comp/app/browser-extension-connector';
import { FeatureTester } from 'comp/browser/feature-tester';
import { FocusDetector } from 'comp/browser/focus-detector';
import { IdleTracker } from 'comp/browser/idle-tracker';
@ -177,6 +178,7 @@ ready(() => {
AppRightsChecker.init();
IdleTracker.init();
UsbListener.init();
BrowserExtensionConnector.init();
setTimeout(() => {
PluginManager.runAutoUpdate();
}, Timeouts.AutoUpdatePluginsAfterStart);

View File

@ -0,0 +1,167 @@
import kdbxweb from 'kdbxweb';
import { box as tweetnaclBox } from 'tweetnacl';
import { RuntimeInfo } from 'const/runtime-info';
import { Launcher } from 'comp/launcher';
import { AppSettingsModel } from 'models/app-settings-model';
const connectedClients = {};
function incrementNonce(nonce) {
// from libsodium/utils.c, like it is in KeePassXC
let i = 0;
let c = 1;
for (; i < nonce.length; ++i) {
c += nonce[i];
nonce[i] = c;
c >>= 8;
}
}
function getClient(request) {
if (!request.clientID) {
throw new Error('Empty clientID');
}
const client = connectedClients[request.clientID];
if (!client) {
throw new Error(`Client not connected: ${request.clientID}`);
}
return client;
}
function decryptRequest(request) {
const client = getClient(request);
if (!request.nonce) {
throw new Error('Empty nonce');
}
if (!request.message) {
throw new Error('Empty message');
}
const nonce = kdbxweb.ByteUtils.base64ToBytes(request.nonce);
const message = kdbxweb.ByteUtils.base64ToBytes(request.message);
const data = tweetnaclBox.open(message, nonce, client.publicKey, client.keys.secretKey);
const json = new TextDecoder().decode(data);
const payload = JSON.parse(json);
if (payload?.action !== request.action) {
throw new Error(`Bad action in decrypted payload`);
}
return payload;
}
function encryptResponse(request, payload) {
const client = getClient(request);
const json = JSON.stringify(payload);
const data = new TextEncoder().encode(json);
let nonce = kdbxweb.ByteUtils.base64ToBytes(request.nonce);
incrementNonce(nonce);
const encrypted = tweetnaclBox(data, nonce, client.publicKey, client.keys.secretKey);
const message = kdbxweb.ByteUtils.bytesToBase64(encrypted);
nonce = kdbxweb.ByteUtils.bytesToBase64(nonce);
return {
action: request.action,
message,
nonce
};
}
const ProtocolHandlers = {
'ping'({ data }) {
return { data };
},
'change-public-keys'({ publicKey, clientID: clientId }) {
const keys = tweetnaclBox.keyPair();
publicKey = kdbxweb.ByteUtils.base64ToBytes(publicKey);
connectedClients[clientId] = { publicKey, keys };
return {
action: 'change-public-keys',
version: RuntimeInfo.version,
publicKey: kdbxweb.ByteUtils.bytesToBase64(keys.publicKey),
success: 'true'
};
},
'get-databasehash'(request) {
decryptRequest(request);
return encryptResponse(request, {
action: 'hash',
version: RuntimeInfo.version,
hash: 'TODO'
});
}
};
const BrowserExtensionConnector = {
init() {
AppSettingsModel.on('change:browserExtension', (model, enabled) => {
if (enabled) {
this.start();
} else {
this.stop();
}
});
if (AppSettingsModel.browserExtension) {
this.start();
}
},
start() {
if (!Launcher) {
this.startWebMessageListener();
}
},
stop() {
if (!Launcher) {
this.stopWebMessageListener();
}
},
startWebMessageListener() {
window.addEventListener('message', this.browserWindowMessage);
},
stopWebMessageListener() {
window.removeEventListener('message', this.browserWindowMessage);
},
browserWindowMessage(e) {
if (e.origin !== location.origin) {
return;
}
if (e.source !== window) {
return;
}
if (e?.data?.kwConnect !== 'request') {
return;
}
let response;
try {
const handler = ProtocolHandlers[e.data.action];
if (!handler) {
throw new Error(`Handler not found: ${e.data.action}`);
}
response = handler(e.data) || {};
} catch (e) {
response = { error: e.message || 'Unknown error' };
}
if (response) {
response.kwConnect = 'response';
postMessage(response, window.location.origin);
}
}
};
export { BrowserExtensionConnector };

View File

@ -47,6 +47,7 @@ const DefaultAppSettings = {
deviceOwnerAuthTimeoutMinutes: 0, // how often master password is required with Touch ID
disableOfflineStorage: false, // don't cache loaded files in offline storage
shortLivedStorageToken: false, // short-lived sessions in cloud storage providers
browserExtension: false, // support browser extension interaction
yubiKeyShowIcon: true, // show an icon to open OTP codes from YubiKey
yubiKeyAutoOpen: false, // auto-load one-time codes when there are open files

View File

@ -14,10 +14,7 @@
<li><a href="https://github.com/patrick-steele-idem/morphdom" target="_blank">morphdom</a><span class="muted-color">, fast and lightweight DOM diffing/patching, &copy; Patrick Steele-Idem &lt;pnidem@gmail.com&gt; (psteeleidem.com)</span></li>
<li><a href="https://lodash.com/" target="_blank">lodash</a><span class="muted-color">, a modern JavaScript utility library delivering modularity, performance & extras, &copy; OpenJS Foundation and other contributors &lt;https://openjsf.org/&gt;</span></li>
<li><a href="https://jquery.com/" target="_blank">jQuery</a><span class="muted-color">, fast, small, and feature-rich JavaScript library, &copy; OpenJS Foundation and other contributors, https://openjsf.org/</span></li>
<li><a href="https://marked.js.org/" target="_blank">marked</a><span class="muted-color">, a markdown parser and compiler, &copy; 2018+, MarkedJS (https://github.com/markedjs/) &copy; 2011-2018, Christopher Jeffrey (https://github.com/chjj/)</span></li>
<li><a href="https://github.com/cure53/DOMPurify" target="_blank">dompurify</a><span class="muted-color">, a DOM-only, super-fast, uber-tolerant XSS sanitizer, &copy; 2015 Mario Heiderich, </span>
<a href="{{licenseLinkApache}}" class="muted-color" target="_blank">Apache-2.0 license</a></li>
<li><a href="https://github.com/TomFrost/node-phonetic" target="_blank">node-phonetic</a><span class="muted-color">, generates unique, pronounceable names, &copy; 2013 Tom Frost</span></li>
</ul>
<h3>Core components</h3>
@ -48,8 +45,13 @@
<h3>Utils</h3>
<ul>
<li><a href="https://marked.js.org/" target="_blank">marked</a><span class="muted-color">, a markdown parser and compiler, &copy; 2018+, MarkedJS (https://github.com/markedjs/) &copy; 2011-2018, Christopher Jeffrey (https://github.com/chjj/)</span></li>
<li><a href="https://github.com/cure53/DOMPurify" target="_blank">dompurify</a><span class="muted-color">, a DOM-only, super-fast, uber-tolerant XSS sanitizer, &copy; 2015 Mario Heiderich, </span>
<a href="{{licenseLinkApache}}" class="muted-color" target="_blank">Apache-2.0 license</a></li>
<li><a href="https://github.com/TomFrost/node-phonetic" target="_blank">node-phonetic</a><span class="muted-color">, generates unique, pronounceable names, &copy; 2013 Tom Frost</span></li>
<li><a href="https://github.com/LazarSoft/jsqrcode" target="_blank">jsqrcode</a><span class="muted-color">, javascript QR code scanner,
<a href="{{licenseLinkApache}}" class="muted-color" target="_blank">Apache-2.0 license</a></span></li>
<li><a href="https://tweetnacl.js.org/" target="_blank">tweetnacl.js</a><span class="muted-color">, port of TweetNaCl cryptographic library to JavaScript, public domain</span></li>
</ul>
<h3>Styles</h3>

View File

@ -56,6 +56,7 @@ function config(options) {
argon2: 'argon2-browser/dist/argon2.js',
marked: devMode ? 'marked/lib/marked.js' : 'marked/marked.min.js',
dompurify: `dompurify/dist/purify${devMode ? '' : '.min'}.js`,
tweetnacl: `tweetnacl/nacl${devMode ? '' : '.min'}.js`,
hbs: 'handlebars/runtime.js',
'argon2-wasm': 'argon2-browser/dist/argon2.wasm',
templates: path.join(rootDir, 'app/templates'),

5
package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "keeweb",
"version": "1.17.4",
"version": "1.17.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.17.4",
"version": "1.17.5",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.13.8",
@ -94,6 +94,7 @@
"svgicons2svgfont": "^9.1.1",
"terser-webpack-plugin": "^5.1.1",
"time-grunt": "2.0.0",
"tweetnacl": "^0.14.5",
"url-loader": "^4.1.1",
"wawoff2": "^1.0.2",
"webpack": "^5.24.3",

View File

@ -96,6 +96,7 @@
"svgicons2svgfont": "^9.1.1",
"terser-webpack-plugin": "^5.1.1",
"time-grunt": "2.0.0",
"tweetnacl": "^0.14.5",
"url-loader": "^4.1.1",
"wawoff2": "^1.0.2",
"webpack": "^5.24.3",