1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 01:03:35 +00:00

[PM-7646][PM-5506] Revert IPC changes (#10946)

* Revert "Remove unnecessary plist keys in desktop_proxy (#10933)"

This reverts commit 4dbb036df1.

* Revert "Fix TestFlight errors caused by desktop_proxy (#10928)"

This reverts commit 40cb4b5353.

* Revert "[PM-5506] Enable electron fuses (#10073)"

This reverts commit 78c5e9c706.

* Revert "[PM-7846] Implement a rust based native messaging proxy and IPC system (#9894)"

This reverts commit 55874b72bf.
This commit is contained in:
Todd Martin
2024-09-09 09:09:17 -04:00
committed by GitHub
parent 2827d338ee
commit ed4d481e4d
33 changed files with 348 additions and 1365 deletions

View File

@@ -1,33 +1,31 @@
import { spawn } from "child_process";
import * as path from "path";
import { NativeMessagingProxy } from "./proxy/native-messaging-proxy";
import { app } from "electron";
// We need to import the other dependencies using `require` since `import` will
// generate `Error: Cannot find module 'electron'`. The cause of this error is
// due to native messaging setting the ELECTRON_RUN_AS_NODE env flag on windows
// which removes the electron module. This flag is needed for stdin/out to work
// properly on Windows.
if (
process.platform === "darwin" &&
process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1)
) {
// If we're on MacOS, we need to support DuckDuckGo's IPC communication,
// which for the moment is launching the Bitwarden process.
// Ideally the browser would instead startup the desktop_proxy process
// when available, but for now we'll just launch it here.
if (process.platform === "darwin") {
// eslint-disable-next-line
const app = require("electron").app;
app.on("ready", () => {
app.dock.hide();
app.on("ready", () => {
app.dock.hide();
});
}
process.stdout.on("error", (e) => {
if (e.code === "EPIPE") {
process.exit(0);
}
});
const proc = spawn(path.join(process.execPath, "..", "desktop_proxy"), process.argv.slice(1), {
cwd: process.cwd(),
stdio: "inherit",
shell: false,
});
proc.on("exit", () => {
process.exit(0);
});
proc.on("error", () => {
process.exit(1);
});
const proxy = new NativeMessagingProxy();
proxy.run();
} else {
// eslint-disable-next-line
const Main = require("./main").Main;

View File

@@ -220,7 +220,6 @@ export class Main {
this.windowMain,
app.getPath("userData"),
app.getPath("exe"),
app.getAppPath(),
);
this.desktopAutofillSettingsService = new DesktopAutofillSettingsService(stateProvider);
@@ -266,21 +265,13 @@ export class Main {
if (browserIntegrationEnabled || ddgIntegrationEnabled) {
// Re-register the native messaging host integrations on startup, in case they are not present
if (browserIntegrationEnabled) {
this.nativeMessagingMain
.generateManifests()
.catch((err) => this.logService.error("Error while generating manifests", err));
this.nativeMessagingMain.generateManifests().catch(this.logService.error);
}
if (ddgIntegrationEnabled) {
this.nativeMessagingMain
.generateDdgManifests()
.catch((err) => this.logService.error("Error while generating DDG manifests", err));
this.nativeMessagingMain.generateDdgManifests().catch(this.logService.error);
}
this.nativeMessagingMain
.listen()
.catch((err) =>
this.logService.error("Error while starting native message listener", err),
);
this.nativeMessagingMain.listen();
}
app.removeAsDefaultProtocolClient("bitwarden");

View File

@@ -1,34 +1,34 @@
import { existsSync, promises as fs } from "fs";
import { Socket } from "net";
import { homedir, userInfo } from "os";
import * as path from "path";
import * as util from "util";
import { ipcMain } from "electron";
import * as ipc from "node-ipc";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { ipc } from "@bitwarden/desktop-napi";
import { isDev } from "../utils";
import { getIpcSocketRoot } from "../proxy/ipc";
import { WindowMain } from "./window.main";
export class NativeMessagingMain {
private ipcServer: ipc.IpcServer | null;
private connected: number[] = [];
private connected: Socket[] = [];
private socket: any;
constructor(
private logService: LogService,
private windowMain: WindowMain,
private userPath: string,
private exePath: string,
private appPath: string,
) {
ipcMain.handle(
"nativeMessaging.manifests",
async (_event: any, options: { create: boolean }) => {
if (options.create) {
this.listen();
try {
await this.listen();
await this.generateManifests();
} catch (e) {
this.logService.error("Error generating manifests: " + e);
@@ -51,8 +51,8 @@ export class NativeMessagingMain {
"nativeMessaging.ddgManifests",
async (_event: any, options: { create: boolean }) => {
if (options.create) {
this.listen();
try {
await this.listen();
await this.generateDdgManifests();
} catch (e) {
this.logService.error("Error generating duckduckgo manifests: " + e);
@@ -72,46 +72,56 @@ export class NativeMessagingMain {
);
}
async listen() {
if (this.ipcServer) {
this.ipcServer.stop();
listen() {
ipc.config.id = "bitwarden";
ipc.config.retry = 1500;
const ipcSocketRoot = getIpcSocketRoot();
if (ipcSocketRoot != null) {
ipc.config.socketRoot = ipcSocketRoot;
}
this.ipcServer = await ipc.IpcServer.listen("bitwarden", (error, msg) => {
switch (msg.kind) {
case ipc.IpcMessageType.Connected: {
this.connected.push(msg.clientId);
this.logService.info("Native messaging client " + msg.clientId + " has connected");
break;
}
case ipc.IpcMessageType.Disconnected: {
const index = this.connected.indexOf(msg.clientId);
if (index > -1) {
this.connected.splice(index, 1);
}
ipc.serve(() => {
ipc.server.on("message", (data: any, socket: any) => {
this.socket = socket;
this.windowMain.win.webContents.send("nativeMessaging", data);
});
this.logService.info("Native messaging client " + msg.clientId + " has disconnected");
break;
ipcMain.on("nativeMessagingReply", (event, msg) => {
if (this.socket != null && msg != null) {
this.send(msg, this.socket);
}
case ipc.IpcMessageType.Message:
this.windowMain.win.webContents.send("nativeMessaging", JSON.parse(msg.message));
break;
}
});
ipc.server.on("connect", (socket: Socket) => {
this.connected.push(socket);
});
ipc.server.on("socket.disconnected", (socket, destroyedSocketID) => {
const index = this.connected.indexOf(socket);
if (index > -1) {
this.connected.splice(index, 1);
}
this.socket = null;
ipc.log("client " + destroyedSocketID + " has disconnected!");
});
});
ipcMain.on("nativeMessagingReply", (event, msg) => {
if (msg != null) {
this.send(msg);
}
});
ipc.server.start();
}
stop() {
this.ipcServer?.stop();
ipc.server.stop();
// Kill all existing connections
this.connected.forEach((socket) => {
if (!socket.destroyed) {
socket.destroy();
}
});
}
send(message: object) {
this.ipcServer?.send(JSON.stringify(message));
send(message: object, socket: any) {
ipc.server.emit(socket, "message", message);
}
async generateManifests() {
@@ -321,20 +331,11 @@ export class NativeMessagingMain {
}
private binaryPath() {
const ext = process.platform === "win32" ? ".exe" : "";
if (isDev()) {
return path.join(
this.appPath,
"..",
"desktop_native",
"target",
"debug",
`desktop_proxy${ext}`,
);
if (process.platform === "win32") {
return path.join(path.dirname(this.exePath), "resources", "native-messaging.bat");
}
return path.join(path.dirname(this.exePath), `desktop_proxy${ext}`);
return this.exePath;
}
private getRegeditInstance() {

View File

@@ -0,0 +1,78 @@
/* 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);
}
}

View File

@@ -0,0 +1,23 @@
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;
}
}

View File

@@ -0,0 +1,95 @@
/* 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);
});
}
}