1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-30 15:13:32 +00:00

[PM-7846] Implement a rust based native messaging proxy and IPC system

This commit is contained in:
Daniel García
2024-06-20 18:11:24 +02:00
parent d92e1b3eca
commit 0a83b8ddaf
26 changed files with 961 additions and 358 deletions

View File

@@ -0,0 +1,102 @@
use std::fs::File;
use futures::{SinkExt, StreamExt};
use log::*;
use tokio_util::codec::LengthDelimitedCodec;
fn init_logging() {
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
let exe = std::env::current_exe().unwrap();
let parent = exe.parent().unwrap();
let level = LevelFilter::Info;
let config = Config::default();
let log_file = File::create(parent.join("proxy.log")).expect("Can't create file");
CombinedLogger::init(vec![
WriteLogger::new(level, config.clone(), log_file),
TermLogger::new(level, config, TerminalMode::Stderr, ColorChoice::Auto),
])
.unwrap();
}
/// Bitwarden IPC Proxy.
///
/// This proxy allows browser extensions to communicate with a desktop application using Native
/// Messaging. This method allows an extension to send and receive messages through the use of
/// stdin/stdout streams.
///
/// However, this also requires the browser to start the process in order for the communication to
/// occur. To overcome this limitation, we implement Inter-Process Communication (IPC) to establish
/// a stable communication channel between the proxy and the running desktop application.
///
/// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop
///
#[tokio::main(flavor = "current_thread")]
async fn main() {
init_logging();
info!("Starting Bitwarden IPC Proxy.");
// Setup two channels, one for sending messages to the desktop application and one for receiving messages
let (in_tx, in_rx) = tokio::sync::mpsc::channel(32);
let (out_tx, mut out_rx) = tokio::sync::mpsc::channel(32);
let mut handle = tokio::spawn(desktop_core::ipc::client::connect(out_tx, in_rx));
// Create a new codec for reading and writing messages from stdin/stdout.
let mut stdin = LengthDelimitedCodec::builder()
.max_frame_length(8192)
.native_endian()
.new_read(tokio::io::stdin());
let mut stdout = LengthDelimitedCodec::builder()
.max_frame_length(8192)
.native_endian()
.new_write(tokio::io::stdout());
loop {
tokio::select! {
// IPC client has finished, so we should exit as well.
_ = &mut handle => {
break;
}
// Receive messages from IPC and print to STDOUT.
msg = out_rx.recv() => {
match msg {
Some(msg) => {
debug!("OUT: {}", msg);
stdout.send(msg.into()).await.unwrap();
}
None => {
// Channel closed, exit.
break;
}
}
},
// Listen to stdin and send messages to ipc processor.
msg = stdin.next() => {
match msg {
Some(Ok(msg)) => {
let m = String::from_utf8(msg.to_vec()).unwrap();
debug!("IN: {}", m);
in_tx.send(m).await.unwrap();
}
Some(Err(e)) => {
// Unexpected error, exit.
error!("Error parsing input: {}", e);
break;
}
None => {
// EOF, exit.
break;
}
}
}
}
}
info!("Exiting.");
}