From cbb9a8dbd02c9e25dec651b151e41eeb8126bac9 Mon Sep 17 00:00:00 2001 From: antelle Date: Sun, 18 Apr 2021 18:50:03 +0200 Subject: [PATCH] sending origin information in the first message --- .../browser-extension-connector.js | 146 ++++++++++++------ desktop/scripts/util/process-utils.js | 40 +++++ .../src/native-messaging-host.cpp | 58 +++---- 3 files changed, 164 insertions(+), 80 deletions(-) create mode 100644 desktop/scripts/util/process-utils.js diff --git a/desktop/scripts/ipc-handlers/browser-extension-connector.js b/desktop/scripts/ipc-handlers/browser-extension-connector.js index b8d0195f..57d2ca5c 100644 --- a/desktop/scripts/ipc-handlers/browser-extension-connector.js +++ b/desktop/scripts/ipc-handlers/browser-extension-connector.js @@ -4,6 +4,7 @@ const path = require('path'); const net = require('net'); const { ipcMain, app } = require('electron'); const { Logger } = require('../logger'); +const { getProcessInfo } = require('../util/process-utils'); ipcMain.handle('browserExtensionConnectorStart', browserExtensionConnectorStart); ipcMain.handle('browserExtensionConnectorStop', browserExtensionConnectorStop); @@ -13,25 +14,34 @@ ipcMain.handle('browserExtensionConnectorSocketEvent', browserExtensionConnector const logger = new Logger('browser-extension-connector'); const MaxIncomingDataLength = 10_000; +const ExtensionOrigins = { + 'chrome-extension://aphablpbogbpmocgkpeeadeljldnphon/': 'keeweb-connect', + 'safari-keeweb-connect': 'keeweb-connect', + 'keeweb-connect@keeweb.info': 'keeweb-connect', + 'chrome-extension://oboonakemofpalcgghocfoadofidjkkk/': 'keepassxc-browser', + 'keepassxc-browser@keepassxc.org': 'keepassxc-browser', + 'chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/': 'keepassxc-browser' +}; let connectedSockets = new Map(); let connectedSocketState = new WeakMap(); let server; +let serverSocketName; let socketId = 0; async function browserExtensionConnectorStart(e, config) { - await prepareBrowserExtensionSocket(config); - const sockName = getBrowserExtensionSocketName(config); + serverSocketName = getBrowserExtensionSocketName(config); + await prepareBrowserExtensionSocket(); - if (isSocketNameTooLong(sockName)) { + if (isSocketNameTooLong(serverSocketName)) { logger.error( "Socket name is too long, browser connection won't be possible, probably OS username is very long.", - sockName + serverSocketName ); return; } - server = net.createServer((socket) => { + server = net.createServer(async (socket) => { socketId++; logger.info(`New connection with socket ${socketId}`); @@ -39,12 +49,10 @@ async function browserExtensionConnectorStart(e, config) { connectedSockets.set(socketId, socket); connectedSocketState.set(socket, { socketId }); - checkSocketIdentity(socket); - socket.on('data', (data) => onSocketData(socket, data)); socket.on('close', () => onSocketClose(socket)); }); - server.listen(sockName); + server.listen(serverSocketName); logger.info('Started'); } @@ -83,22 +91,21 @@ function getBrowserExtensionSocketName(config) { } } -function prepareBrowserExtensionSocket(config) { +function prepareBrowserExtensionSocket() { return new Promise((resolve) => { if (process.platform === 'darwin') { - const sockName = getBrowserExtensionSocketName(config); - fs.access(sockName, fs.constants.F_OK, (err) => { + fs.access(serverSocketName, fs.constants.F_OK, (err) => { if (err) { - const dir = path.dirname(sockName); + const dir = path.dirname(serverSocketName); fs.mkdir(dir, () => resolve()); } else { - fs.unlink(sockName, () => resolve()); + fs.unlink(serverSocketName, () => resolve()); } }); } else if (process.platform === 'win32') { return resolve(); } else { - fs.unlink(getBrowserExtensionSocketName(config), () => resolve()); + fs.unlink(serverSocketName, () => resolve()); } }); } @@ -108,38 +115,6 @@ function isSocketNameTooLong(socketName) { return socketName.length > maxLength; } -function checkSocketIdentity(socket) { - const state = connectedSocketState.get(socket); - if (!state) { - return; - } - - // TODO: implement this - - state.active = true; - state.appName = 'TODO'; - state.extensionName = 'TODO'; - state.pid = 0; - state.supportsNotifications = state.appName !== 'Safari'; - - logger.info( - `Socket ${state.socketId} activated`, - `app: ${state.appName}`, - `extension: ${state.extensionName}`, - `pid: ${state.pid}` - ); - - sendToRenderer('browserExtensionSocketConnected', state.socketId, { - connectionId: state.socketId, - appName: state.appName, - extensionName: state.extensionName, - pid: state.pid, - supportsNotifications: state.supportsNotifications - }); - - processPendingSocketData(socket); -} - function onSocketClose(socket) { const state = connectedSocketState.get(socket); connectedSocketState.delete(socket); @@ -174,7 +149,7 @@ function onSocketData(socket, data) { async function processPendingSocketData(socket) { const state = connectedSocketState.get(socket); - if (!state?.active) { + if (!state) { return; } if (!state.pendingData || state.processingData) { @@ -218,6 +193,11 @@ async function processPendingSocketData(socket) { return; } + if (!state.active) { + await processFirstMessageFromSocket(socket, request); + return; + } + logger.debug(`Extension[${state.socketId}] -> KeeWeb`, request); if (!request) { @@ -251,6 +231,78 @@ async function processPendingSocketData(socket) { sendToRenderer('browserExtensionSocketRequest', state.socketId, request); } +async function processFirstMessageFromSocket(socket, message) { + const state = connectedSocketState.get(socket); + if (!state) { + return; + } + + logger.debug(`Init connection ${state.socketId}`, message); + + if (!message.origin) { + logger.error('Empty origin'); + socket.destroy(); + return; + } + if (!message.pid) { + logger.error('Empty pid'); + socket.destroy(); + return; + } + + const extensionName = ExtensionOrigins[message.origin] || 'unknown'; + const isSafari = message.origin === 'safari-keeweb-connect'; + let appName; + + if (isSafari) { + appName = 'Safari'; + } else { + if (!message.ppid) { + logger.error('Empty ppid'); + socket.destroy(); + return; + } + + let parentProcessInfo; + try { + parentProcessInfo = await getProcessInfo(message.ppid); + } catch (e) { + logger.error(`Cannot get info for PID ${message.ppid}: ${e}`); + socket.destroy(); + return; + } + + appName = parentProcessInfo.commandLine.split('/').pop(); + appName = appName[0].toUpperCase() + appName.substr(1); + } + + state.active = true; + state.appName = appName; + state.extensionName = extensionName; + state.pid = message.pid; + state.ppid = message.ppid; + state.supportsNotifications = state.appName !== 'Safari'; + state.processingData = false; + + logger.info( + `Socket ${state.socketId} activated for ` + + `app: "${state.appName}", ` + + `extension: "${state.extensionName}", ` + + `pid: ${state.pid}, ` + + `ppid: ${state.ppid}` + ); + + sendToRenderer('browserExtensionSocketConnected', state.socketId, { + connectionId: state.socketId, + appName: state.appName, + extensionName: state.extensionName, + pid: state.pid, + supportsNotifications: state.supportsNotifications + }); + + processPendingSocketData(socket); +} + function sendResultToSocket(socketId, result) { const socket = connectedSockets.get(socketId); if (socket) { diff --git a/desktop/scripts/util/process-utils.js b/desktop/scripts/util/process-utils.js new file mode 100644 index 00000000..b2793904 --- /dev/null +++ b/desktop/scripts/util/process-utils.js @@ -0,0 +1,40 @@ +const childProcess = require('child_process'); + +function getProcessInfo(pid) { + return new Promise((resolve, reject) => { + const process = childProcess.spawn('/bin/ps', ['-opid=,ppid=,command=', '-p', pid]); + + const data = []; + process.stdout.on('data', (chunk) => data.push(chunk)); + + process.on('close', () => { + const output = Buffer.concat(data).toString(); + try { + const result = parsePsOutput(output); + if (result.pid !== pid) { + throw new Error(`PS pid mismatch: ${result.pid} <> ${pid}`); + } + resolve(result); + } catch (e) { + reject(e); + } + }); + process.on('error', (e) => { + reject(e); + }); + }); +} + +function parsePsOutput(output) { + const match = output.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/); + if (!match) { + throw new Error(`Bad PS output: ${output}`); + } + return { + pid: match[1] | 0, + parentPid: match[2] | 0, + commandLine: match[3] + }; +} + +module.exports = { getProcessInfo }; diff --git a/extension/native-messaging-host/src/native-messaging-host.cpp b/extension/native-messaging-host/src/native-messaging-host.cpp index 247a4410..a0f1e025 100644 --- a/extension/native-messaging-host/src/native-messaging-host.cpp +++ b/extension/native-messaging-host/src/native-messaging-host.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -12,20 +11,8 @@ // https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-protocol // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side -constexpr std::array kAllowedOrigins = { - // KeeWeb Connect: Chrome - std::string_view("chrome-extension://npmnaajonabmkjekongmjhdjpjdlhpkp/"), - // KeeWeb Connect: Firefox - std::string_view("keeweb-connect@keeweb.info"), - // KeePassXC-Browser: Chrome - std::string_view("chrome-extension://oboonakemofpalcgghocfoadofidjkkk/"), - // KeePassXC-Browser: Firefox - std::string_view("keepassxc-browser@keepassxc.org"), - // KeePassXC-Browser: Edge - std::string_view("chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"), -}; - struct State { + std::string origin; uv_stream_t *tty_in = nullptr; uv_stream_t *tty_out = nullptr; uv_stream_t *keeweb_pipe = nullptr; @@ -44,24 +31,6 @@ void process_stdout_queue(); void close_keeweb_pipe(); void connect_keeweb_pipe(); -bool check_args(int argc, char *argv[]) { - if (argc < 2) { - std::cerr << "Expected origin argument" << std::endl; - return false; - } - - for (int arg = 1; arg < argc; arg++) { - std::string origin = argv[arg]; - auto found = std::find(kAllowedOrigins.begin(), kAllowedOrigins.end(), origin); - if (found != kAllowedOrigins.end()) { - return true; - } - } - std::cerr << "Bad origin" << std::endl; - - return false; -} - void alloc_buf(uv_handle_t *, size_t size, uv_buf_t *buf) { buf->base = new char[size]; buf->len = static_cast(size); @@ -235,6 +204,25 @@ void connect_keeweb_pipe() { void start_reading_stdin() { uv_read_start(state.tty_in, alloc_buf, stdin_read_cb); } +void push_first_message_to_keeweb() { + auto origin = state.origin; + std::replace(origin.begin(), origin.end(), '"', '\''); + + auto message = "{\"pid\":" + std::to_string(uv_os_getpid()) + + ",\"ppid\":" + std::to_string(uv_os_getppid()) + ",\"origin\":\"" + origin + + "\"}"; + + auto message_length = message.length() + sizeof(uint32_t); + auto data = new char[message_length]; + auto size_ptr = reinterpret_cast(data); + + *size_ptr = message.length(); + memcpy(data + sizeof(uint32_t), message.c_str(), message.length()); + + state.pending_to_keeweb.emplace( + uv_buf_init(data, static_cast(message_length))); +} + void init_tty() { auto stdin_tty = new uv_tty_t{}; uv_tty_init(uv_default_loop(), stdin_tty, 0, 0); @@ -246,9 +234,13 @@ void init_tty() { } int main(int argc, char *argv[]) { - if (!check_args(argc, argv)) { + if (argc < 2) { + std::cerr << "Expected origin argument" << std::endl; return 1; } + state.origin = argv[1]; + + push_first_message_to_keeweb(); init_tty(); start_reading_stdin();