keeweb/app/scripts/comp/launcher/native-modules.js

262 lines
7.5 KiB
JavaScript
Raw Normal View History

2021-01-10 14:31:33 +01:00
import kdbxweb from 'kdbxweb';
import { Events } from 'framework/events';
import { Logger } from 'util/logger';
import { Launcher } from 'comp/launcher';
import { Timeouts } from 'const/timeouts';
let NativeModules;
if (Launcher) {
const logger = new Logger('native-module-connector');
let host;
let callId = 0;
let promises = {};
let ykChalRespCallbacks = {};
const handlers = {
yubikeys(numYubiKeys) {
Events.emit('native-modules-yubikeys', { numYubiKeys });
},
log(...args) {
logger.info('Message from host', ...args);
},
result({ callId, result, error }) {
const promise = promises[callId];
if (promise) {
delete promises[callId];
if (error) {
logger.error('Received an error', promise.cmd, error);
promise.reject(error);
} else {
promise.resolve(result);
}
}
},
2021-01-30 18:39:25 +01:00
yubiKeyChallengeResponseResult({ callbackId, error, result }) {
const callback = ykChalRespCallbacks[callbackId];
if (callback) {
const willBeCalledAgain = error && error.touchRequested;
if (!willBeCalledAgain) {
delete ykChalRespCallbacks[callbackId];
}
callback(error, result);
}
}
};
NativeModules = {
startHost() {
if (host) {
return;
}
logger.debug('Starting native module host');
const path = Launcher.req('path');
const appContentRoot = Launcher.remoteApp().getAppContentRoot();
const mainModulePath = path.join(appContentRoot, 'native-module-host.js');
const { fork } = Launcher.req('child_process');
host = fork(mainModulePath);
host.on('message', (message) => this.hostCallback(message));
host.on('error', (e) => this.hostError(e));
host.on('exit', (code, sig) => this.hostExit(code, sig));
if (this.usbListenerRunning) {
this.call('start-usb');
}
},
hostError(e) {
logger.error('Host error', e);
},
hostExit(code, sig) {
logger.error(`Host exited with code ${code} and signal ${sig}`);
host = null;
const err = new Error('Native module host crashed');
for (const promise of Object.values(promises)) {
promise.reject(err);
}
promises = {};
for (const callback of Object.values(ykChalRespCallbacks)) {
callback(err);
}
ykChalRespCallbacks = {};
if (code !== 0) {
this.autoRestartHost();
}
},
hostCallback(message) {
const { cmd, args } = message;
// logger.debug('Callback', cmd, args);
if (handlers[cmd]) {
handlers[cmd](...args);
} else {
logger.error('No callback', cmd);
}
},
autoRestartHost() {
setTimeout(() => {
try {
this.startHost();
} catch (e) {
logger.error('Native module host failed to auto-restart', e);
}
}, Timeouts.NativeModuleHostRestartTime);
},
call(cmd, ...args) {
return new Promise((resolve, reject) => {
if (!host) {
try {
this.startHost();
} catch (e) {
return reject(e);
}
}
callId++;
if (callId === Number.MAX_SAFE_INTEGER) {
callId = 1;
}
// logger.debug('Call', cmd, args, callId);
promises[callId] = { cmd, resolve, reject };
host.send({ cmd, args, callId });
});
},
2021-01-10 14:31:33 +01:00
makeXoredValue(val) {
const data = Buffer.from(val);
const random = Buffer.from(kdbxweb.Random.getBytes(data.length));
for (let i = 0; i < data.length; i++) {
data[i] ^= random[i];
}
const result = { data: [...data], random: [...random] };
data.fill(0);
random.fill(0);
return result;
},
readXoredValue(val) {
const data = Buffer.from(val.data);
const random = Buffer.from(val.random);
for (let i = 0; i < data.length; i++) {
data[i] ^= random[i];
}
val.data.fill(0);
val.random.fill(0);
return data;
},
startUsbListener() {
2021-01-30 18:39:25 +01:00
this.call('startUsbListener');
this.usbListenerRunning = true;
},
stopUsbListener() {
this.usbListenerRunning = false;
if (host) {
2021-01-30 18:39:25 +01:00
this.call('stopUsbListener');
}
},
getYubiKeys(config) {
2021-01-30 18:39:25 +01:00
return this.call('getYubiKeys', config);
},
yubiKeyChallengeResponse(yubiKey, challenge, slot, callback) {
ykChalRespCallbacks[callId] = callback;
2021-01-30 18:39:25 +01:00
return this.call('yubiKeyChallengeResponse', yubiKey, challenge, slot, callId);
},
yubiKeyCancelChallengeResponse() {
if (host) {
2021-01-30 18:39:25 +01:00
this.call('yubiKeyCancelChallengeResponse');
}
},
argon2(password, salt, options) {
return this.call('argon2', password, salt, options);
2021-01-10 14:31:33 +01:00
},
2021-01-10 16:56:53 +01:00
hardwareEncrypt: async (value) => {
2021-01-10 14:31:33 +01:00
const { ipcRenderer } = Launcher.electron();
2021-01-10 16:56:53 +01:00
value = NativeModules.makeXoredValue(value);
2021-01-30 18:39:25 +01:00
const encrypted = await ipcRenderer.invoke('hardwareEncrypt', value);
2021-01-10 16:56:53 +01:00
return NativeModules.readXoredValue(encrypted);
},
hardwareDecrypt: async (value, touchIdPrompt) => {
const { ipcRenderer } = Launcher.electron();
value = NativeModules.makeXoredValue(value);
2021-01-30 18:39:25 +01:00
const decrypted = await ipcRenderer.invoke('hardwareDecrypt', value, touchIdPrompt);
2021-01-10 16:56:53 +01:00
return NativeModules.readXoredValue(decrypted);
2021-01-30 14:16:32 +01:00
},
kbdGetActiveWindow(options) {
2021-01-30 18:39:25 +01:00
return this.call('kbdGetActiveWindow', options);
2021-01-30 14:16:32 +01:00
},
kbdGetActivePid() {
2021-01-30 18:39:25 +01:00
return this.call('kbdGetActivePid');
2021-01-30 14:16:32 +01:00
},
kbdShowWindow(win) {
2021-01-30 18:39:25 +01:00
return this.call('kbdShowWindow', win);
2021-01-30 14:16:32 +01:00
},
kbdText(str) {
2021-01-30 18:39:25 +01:00
return this.call('kbdText', str);
2021-01-30 14:16:32 +01:00
},
kbdKeyPress(code, modifiers) {
2021-01-30 18:39:25 +01:00
return this.call('kbdKeyPress', code, modifiers);
2021-01-30 14:16:32 +01:00
},
kbdShortcut(code, modifiers) {
2021-01-30 18:39:25 +01:00
return this.call('kbdShortcut', code, modifiers);
2021-01-30 18:24:42 +01:00
},
2021-01-30 20:22:13 +01:00
// kbdKeyMoveWithCode(down, code, modifiers) {
// return this.call('kbdKeyMoveWithCode', down, code, modifiers);
// },
//
// kbdKeyMoveWithModifier(down, modifiers) {
// return this.call('kbdKeyMoveWithModifier', down, modifiers);
// },
kbdKeyPressWithCharacter(character, code, modifiers) {
return this.call('kbdKeyPressWithCharacter', character, code, modifiers);
2021-01-30 18:24:42 +01:00
},
2021-01-30 20:22:13 +01:00
kbdEnsureModifierNotPressed() {
2021-01-30 18:39:25 +01:00
return this.call('kbdEnsureModifierNotPressed');
}
};
2021-01-10 14:31:33 +01:00
global.NativeModules = NativeModules;
}
export { NativeModules };