mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 18:53:29 +00:00
[PM-7846] Implement a rust based native messaging proxy and IPC system (#9894)
* [PM-7846] Implement a rust based native messaging proxy and IPC system
* Only build desktop_proxy
* Bundle the desktop_proxy file
* Make sys deps optional for the proxy
* Restore accidentally deleted after-sign
* Update native cache to contain dist folder
* Add some test logging
* Native module cache seems very aggressive
* Fix invalid directory
* Fix debug print
* Remove cache force
* Remove cache debug code
* Only log to file in debug builds
* Place the binary in the correct place for mac and make sure it's signed
* Fix platform paths
* Test unsigned appx
* Revert "Test unsigned appx"
This reverts commit e47535440a.
* Fix comment
* Remove logs
* Use debug builds in native code, and test private path on MacOS
* Add connected message
* Update IPC API comments
* Update linux to also use XDG_ dir
* Update main.rs comment
* Improve docs and split some tasks spawned into separate functions
* Update send docs and return number of elements sent
* Mark `listen` as async to ensure it runs in a tokio context, handle errors better
* Add log on client channel closed
* Move binary to MacOS folder, and sign it manually so it gets the correct entitlements
* Fix some review comments
* Run prettier
* Added missing zbus_polkit dep
* Extract magic number and increase it to match spec
* Comment fix
* Use Napi object, combine nativeBinding export, always log to file
* Missed one comment
* Remove unnecessary generics
* Correct comment
* Select only codesigning identities
* Filter certificates
* Also add local dev cert
* Remove log
* Fix package ID
* debug_assert won't run the pop() in release mode
* Better error messages
* Fix review comments
* Remove unnecessary comment
* Update napi generated TS file
* Temporary fix for DDG
This commit is contained in:
@@ -1,78 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { createHash } from "crypto";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join as path_join } from "path";
|
||||
|
||||
import * as ipc from "node-ipc";
|
||||
|
||||
export function getIpcSocketRoot(): string | null {
|
||||
let socketRoot = null;
|
||||
|
||||
switch (process.platform) {
|
||||
case "darwin": {
|
||||
const ipcSocketRootDir = path_join(homedir(), "tmp");
|
||||
if (!existsSync(ipcSocketRootDir)) {
|
||||
mkdirSync(ipcSocketRootDir);
|
||||
}
|
||||
socketRoot = ipcSocketRootDir + "/";
|
||||
break;
|
||||
}
|
||||
case "win32": {
|
||||
// Let node-ipc use a unique IPC pipe //./pipe/xxxxxxxxxxxxxxxxx.app.bitwarden per user.
|
||||
// Hashing prevents problems with reserved characters and file length limitations.
|
||||
socketRoot = createHash("sha1").update(homedir()).digest("hex") + ".";
|
||||
}
|
||||
}
|
||||
return socketRoot;
|
||||
}
|
||||
|
||||
ipc.config.id = "proxy";
|
||||
ipc.config.retry = 1500;
|
||||
ipc.config.logger = console.warn; // Stdout is used for native messaging
|
||||
const ipcSocketRoot = getIpcSocketRoot();
|
||||
if (ipcSocketRoot != null) {
|
||||
ipc.config.socketRoot = ipcSocketRoot;
|
||||
}
|
||||
|
||||
export default class IPC {
|
||||
onMessage: (message: object) => void;
|
||||
|
||||
private connected = false;
|
||||
|
||||
connect() {
|
||||
ipc.connectTo("bitwarden", () => {
|
||||
ipc.of.bitwarden.on("connect", () => {
|
||||
this.connected = true;
|
||||
console.error("## connected to bitwarden desktop ##");
|
||||
|
||||
// Notify browser extension, connection is established to desktop application.
|
||||
this.onMessage({ command: "connected" });
|
||||
});
|
||||
|
||||
ipc.of.bitwarden.on("disconnect", () => {
|
||||
this.connected = false;
|
||||
console.error("disconnected from world");
|
||||
|
||||
// Notify browser extension, no connection to desktop application.
|
||||
this.onMessage({ command: "disconnected" });
|
||||
});
|
||||
|
||||
ipc.of.bitwarden.on("message", (message: any) => {
|
||||
this.onMessage(message);
|
||||
});
|
||||
|
||||
ipc.of.bitwarden.on("error", (err: any) => {
|
||||
console.error("error", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
send(json: object) {
|
||||
ipc.of.bitwarden.emit("message", json);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import IPC from "./ipc";
|
||||
import NativeMessage from "./nativemessage";
|
||||
|
||||
// Proxy is a lightweight application which provides bi-directional communication
|
||||
// between the browser extension and a running desktop application.
|
||||
//
|
||||
// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop
|
||||
export class NativeMessagingProxy {
|
||||
private ipc: IPC;
|
||||
private nativeMessage: NativeMessage;
|
||||
|
||||
constructor() {
|
||||
this.ipc = new IPC();
|
||||
this.nativeMessage = new NativeMessage(this.ipc);
|
||||
}
|
||||
|
||||
run() {
|
||||
this.ipc.connect();
|
||||
this.nativeMessage.listen();
|
||||
|
||||
this.ipc.onMessage = this.nativeMessage.send;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import IPC from "./ipc";
|
||||
|
||||
// Mostly based on the example from MDN,
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging
|
||||
export default class NativeMessage {
|
||||
ipc: IPC;
|
||||
|
||||
constructor(ipc: IPC) {
|
||||
this.ipc = ipc;
|
||||
}
|
||||
|
||||
send(message: object) {
|
||||
const messageBuffer = Buffer.from(JSON.stringify(message));
|
||||
|
||||
const headerBuffer = Buffer.alloc(4);
|
||||
headerBuffer.writeUInt32LE(messageBuffer.length, 0);
|
||||
|
||||
process.stdout.write(Buffer.concat([headerBuffer, messageBuffer]));
|
||||
}
|
||||
|
||||
listen() {
|
||||
let payloadSize: number = null;
|
||||
|
||||
// A queue to store the chunks as we read them from stdin.
|
||||
// This queue can be flushed when `payloadSize` data has been read
|
||||
const chunks: any = [];
|
||||
|
||||
// Only read the size once for each payload
|
||||
const sizeHasBeenRead = () => Boolean(payloadSize);
|
||||
|
||||
// All the data has been read, reset everything for the next message
|
||||
const flushChunksQueue = () => {
|
||||
payloadSize = null;
|
||||
chunks.splice(0);
|
||||
};
|
||||
|
||||
const processData = () => {
|
||||
// Create one big buffer with all all the chunks
|
||||
const stringData = Buffer.concat(chunks);
|
||||
console.error(stringData);
|
||||
|
||||
// The browser will emit the size as a header of the payload,
|
||||
// if it hasn't been read yet, do it.
|
||||
// The next time we'll need to read the payload size is when all of the data
|
||||
// of the current payload has been read (ie. data.length >= payloadSize + 4)
|
||||
if (!sizeHasBeenRead()) {
|
||||
try {
|
||||
payloadSize = stringData.readUInt32LE(0);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the data we have read so far is >= to the size advertised in the header,
|
||||
// it means we have all of the data sent.
|
||||
// We add 4 here because that's the size of the bytes that old the payloadSize
|
||||
if (stringData.length >= payloadSize + 4) {
|
||||
// Remove the header
|
||||
const contentWithoutSize = stringData.slice(4, payloadSize + 4).toString();
|
||||
|
||||
// Reset the read size and the queued chunks
|
||||
flushChunksQueue();
|
||||
|
||||
const json = JSON.parse(contentWithoutSize);
|
||||
|
||||
// Forward to desktop application
|
||||
this.ipc.send(json);
|
||||
}
|
||||
};
|
||||
|
||||
process.stdin.on("readable", () => {
|
||||
// A temporary variable holding the nodejs.Buffer of each
|
||||
// chunk of data read off stdin
|
||||
let chunk = null;
|
||||
|
||||
// Read all of the available data
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
while ((chunk = process.stdin.read()) !== null) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
try {
|
||||
processData();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on("end", () => {
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user