From 41f1998d34d4d6841b079f8db4535237c56ab10d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 15:29:49 +0200 Subject: [PATCH 1/5] Add initial noise impl --- Cargo.lock | 23 +- crates/bitwarden-ipc/Cargo.toml | 2 + crates/bitwarden-ipc/src/error.rs | 3 + crates/bitwarden-ipc/src/ipc_client.rs | 38 ++- .../src/traits/communication_backend.rs | 44 ++- .../src/traits/crypto_provider.rs | 2 + .../src/traits/crypto_provider_impl.rs | 269 ++++++++++++++++++ crates/bitwarden-ipc/src/traits/mod.rs | 2 + crates/bitwarden-ipc/src/wasm/error.rs | 4 + 9 files changed, 380 insertions(+), 7 deletions(-) create mode 100644 crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs diff --git a/Cargo.lock b/Cargo.lock index 6024913fd..114ed4920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,10 +549,12 @@ name = "bitwarden-ipc" version = "1.0.0" dependencies = [ "bitwarden-error", + "ciborium", "js-sys", "serde", "serde_json", - "thiserror 1.0.69", + "snow", + "thiserror 2.0.12", "tokio", "tsify-next", "wasm-bindgen", @@ -1254,6 +1256,7 @@ dependencies = [ "fiat-crypto", "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -2358,7 +2361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3671,6 +3674,22 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core", + "rustc_version", + "sha2", + "subtle", +] + [[package]] name = "socket2" version = "0.5.8" diff --git a/crates/bitwarden-ipc/Cargo.toml b/crates/bitwarden-ipc/Cargo.toml index 44b5ab31f..0fff02d1c 100644 --- a/crates/bitwarden-ipc/Cargo.toml +++ b/crates/bitwarden-ipc/Cargo.toml @@ -19,9 +19,11 @@ wasm = [ [dependencies] bitwarden-error = { workspace = true } +ciborium = "0.2.2" js-sys = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } +snow = "0.9.6" thiserror = { workspace = true } tokio = { features = ["sync", "time"], workspace = true } tsify-next = { workspace = true, optional = true } diff --git a/crates/bitwarden-ipc/src/error.rs b/crates/bitwarden-ipc/src/error.rs index 16575ee6a..d02a23f56 100644 --- a/crates/bitwarden-ipc/src/error.rs +++ b/crates/bitwarden-ipc/src/error.rs @@ -7,6 +7,9 @@ pub enum SendError { #[error("Communication error: {0}")] Communication(Com), + + #[error("Receive Error")] + ReceiveError } #[derive(Clone, Debug, Error, PartialEq, Eq)] diff --git a/crates/bitwarden-ipc/src/ipc_client.rs b/crates/bitwarden-ipc/src/ipc_client.rs index e7fb13a15..db8b87ca9 100644 --- a/crates/bitwarden-ipc/src/ipc_client.rs +++ b/crates/bitwarden-ipc/src/ipc_client.rs @@ -102,8 +102,7 @@ mod tests { use crate::{ endpoint::Endpoint, traits::{ - tests::{TestCommunicationBackend, TestCommunicationBackendReceiveError}, - InMemorySessionRepository, NoEncryptionCryptoProvider, + tests::{TestCommunicationBackend, TestCommunicationBackendReceiveError, TestTwoWayCommunicationBackend}, BitwardenCryptoProvider, InMemorySessionRepository, NoEncryptionCryptoProvider }, }; @@ -188,7 +187,7 @@ mod tests { destination: Endpoint::BrowserBackground, topic: None, }; - let crypto_provider = NoEncryptionCryptoProvider; + let crypto_provider = BitwardenCryptoProvider; let communication_provider = TestCommunicationBackend::new(); let session_map = InMemorySessionRepository::new(HashMap::new()); let client = IpcClient::new(crypto_provider, communication_provider.clone(), session_map); @@ -369,4 +368,37 @@ mod tests { Err(TypedReceiveError::Typing(serde_json::Error { .. })) )); } + + #[tokio::test] + async fn communication_provider_ping_pong() { + let (sender_communication_provider, receiver_communication_provider) = + TestTwoWayCommunicationBackend::new(); + + // start thread + let a = tokio::spawn(async move { + let receiver_crypto_provider = BitwardenCryptoProvider; + let receiver_session_map = InMemorySessionRepository::new(HashMap::new()); + let receiver_client = IpcClient::new(receiver_crypto_provider, receiver_communication_provider.clone(), receiver_session_map); + let received_message = receiver_client.receive(None, None).await.unwrap(); + println!("Received message: {:?}", String::from_utf8(received_message.payload)); + }); + + let b = tokio::spawn(async move { + let message = OutgoingMessage { + payload: "Hello, world!".as_bytes().to_vec(), + destination: Endpoint::BrowserBackground, + topic: None, + }; + let sender_crypto_provider = BitwardenCryptoProvider; + let sender_session_map = InMemorySessionRepository::new(HashMap::new()); + let sender_client = IpcClient::new(sender_crypto_provider, sender_communication_provider.clone(), sender_session_map); + sender_client.send(message.clone()).await.unwrap(); + //sender_client.send(message.clone()).await.unwrap(); + }); + + // sleep + tokio::time::sleep(Duration::from_secs(3)).await; + println!("Waiting for tasks to finish..."); + let _ = tokio::try_join!(a, b); + } } diff --git a/crates/bitwarden-ipc/src/traits/communication_backend.rs b/crates/bitwarden-ipc/src/traits/communication_backend.rs index 7e3b721e6..132ad7b8e 100644 --- a/crates/bitwarden-ipc/src/traits/communication_backend.rs +++ b/crates/bitwarden-ipc/src/traits/communication_backend.rs @@ -23,10 +23,10 @@ pub trait CommunicationBackend { } #[cfg(test)] pub mod tests { - use std::{collections::VecDeque, rc::Rc}; + use std::{collections::VecDeque, rc::Rc, sync::Arc}; use thiserror::Error; - use tokio::sync::RwLock; + use tokio::sync::{mpsc::{self, Receiver, Sender}, Mutex, RwLock}; use super::*; @@ -80,4 +80,44 @@ pub mod tests { } } } + + #[derive(Debug, Clone)] + pub struct TestTwoWayCommunicationBackend { + outgoing: Sender, + incoming: Arc>>, + } + + impl TestTwoWayCommunicationBackend { + pub fn new() -> (Self, Self) { + let (outgoing, incoming) = mpsc::channel(10); + let (outgoing1, incoming1) = mpsc::channel(10); + let one = TestTwoWayCommunicationBackend { + outgoing: outgoing, + incoming: Arc::new(Mutex::new(incoming1)), + }; + let two = TestTwoWayCommunicationBackend { + outgoing: outgoing1, + incoming: Arc::new(Mutex::new(incoming)), + }; + (one, two) + } + } + + impl CommunicationBackend for TestTwoWayCommunicationBackend { + type SendError = (); + type ReceiveError = TestCommunicationBackendReceiveError; + + async fn send(&self, message: OutgoingMessage) -> Result<(), Self::SendError> { + self.outgoing.send(message).await.unwrap(); + Ok(()) + } + + async fn receive(&self) -> Result { + let mut receiver = self.incoming.lock().await; + let message = receiver.recv().await.unwrap(); + Ok(IncomingMessage { payload:message.payload, destination: message.destination, source: crate::endpoint::Endpoint::DesktopRenderer, topic: None }) + } + } + + } diff --git a/crates/bitwarden-ipc/src/traits/crypto_provider.rs b/crates/bitwarden-ipc/src/traits/crypto_provider.rs index e1b729954..8fa9de43a 100644 --- a/crates/bitwarden-ipc/src/traits/crypto_provider.rs +++ b/crates/bitwarden-ipc/src/traits/crypto_provider.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + use super::{CommunicationBackend, SessionRepository}; use crate::{ error::{ReceiveError, SendError}, diff --git a/crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs b/crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs new file mode 100644 index 000000000..edf71ac28 --- /dev/null +++ b/crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs @@ -0,0 +1,269 @@ +use core::panic; +use std::{sync::{Arc, Mutex}, vec}; + +use serde::{Deserialize, Serialize}; +use snow::TransportState; + +use crate::{error::{ReceiveError, SendError}, message::{IncomingMessage, OutgoingMessage}}; + +use super::{CommunicationBackend, CryptoProvider, SessionRepository}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +enum BitwardenCryptoProtocolIdentifier { + NoiseCbor, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct BitwardenCryptoProtocolFrame { + protocol_identifier: BitwardenCryptoProtocolIdentifier, + protocol_frame: Vec, +} + +impl BitwardenCryptoProtocolFrame { + fn noise_frame(frame: BitwardenNoiseFrame) -> Self { + BitwardenCryptoProtocolFrame { + protocol_identifier: BitwardenCryptoProtocolIdentifier::NoiseCbor, + protocol_frame: frame.as_cbor(), + } + } + + fn as_cbor(&self) -> Vec { + let mut buffer = Vec::new(); + ciborium::into_writer(self, &mut buffer).unwrap(); + buffer + } + + fn from_cbor(buffer: &[u8]) -> Self { + ciborium::from_reader(buffer).unwrap() + } +} + + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum BitwardenNoiseFrame { + HandshakeOne { + params: String, + payload: Vec, + }, + HandshakeTwo { + payload: Vec, + }, + HandshakeThree { + payload: Vec, + }, + Payload { + payload: Vec, + } +} + +impl BitwardenNoiseFrame { + fn as_cbor(&self) -> Vec { + let mut buffer = Vec::new(); + ciborium::into_writer(self, &mut buffer).unwrap(); + buffer + } + + fn from_cbor(buffer: &[u8]) -> Self { + ciborium::from_reader(buffer).unwrap() + } +} + +pub struct BitwardenCryptoProvider; +#[derive(Clone, Debug)] +pub struct NoiseCryptoProviderState { + state: Arc>>, +} + +impl CryptoProvider for BitwardenCryptoProvider +where + Com: CommunicationBackend, + Ses: SessionRepository, +{ + type Session = NoiseCryptoProviderState; + type SendError = Com::SendError; + type ReceiveError = Com::ReceiveError; + + async fn send( + &self, + communication: &Com, + sessions: &Ses, + message: OutgoingMessage, + ) -> Result<(), SendError> { + let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { + panic!("Session not found"); + }; + let crypto_state = match crypto_state_opt { + Some(state) => state, + None => { + let new_state = NoiseCryptoProviderState { state: Arc::new(Mutex::new(None)) }; + sessions.save(message.destination.clone(), new_state.clone()).await; + new_state + } + }; + + // Session is not established yet. Establish it. + if crypto_state.state.lock().unwrap().is_none() { + let mut initiator = snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap()).build_initiator().unwrap(); + + // Send Handshake One + let mut buffer = vec![0u8; 65536]; + let res = initiator.write_message(&[], &mut buffer).unwrap(); + buffer = buffer[..res].to_vec(); + println!("Handshake One: {:?}", res); + let handshake_one = BitwardenNoiseFrame::HandshakeOne { params: "Noise_NN_25519_ChaChaPoly_BLAKE2s".to_string(), payload: buffer.to_vec() }; + let msg = OutgoingMessage { + payload: BitwardenCryptoProtocolFrame::noise_frame(handshake_one).as_cbor(), + destination: message.destination.clone(), + topic: None, + }; + println!("Sending Handshake One: {:?}", msg); + communication.send(msg).await.map_err(SendError::Communication)?; + println!("Sent Handshake One"); + + // sleep + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // Receive Handshake Two + let mut buffer = Vec::new(); + println!("Waiting for Handshake Two"); + let resp = communication.receive().await.map_err(|_| SendError::ReceiveError)?; + let handshake_two = BitwardenCryptoProtocolFrame::from_cbor(resp.payload.as_slice()); + let resp = BitwardenNoiseFrame::from_cbor(handshake_two.protocol_frame.as_slice()); + let BitwardenNoiseFrame::HandshakeTwo { payload } = resp else { + panic!("Expected Handshake Two"); + }; + println!("Received Handshake Two: {:?}", &payload); + initiator.read_message(&payload, &mut buffer).unwrap(); + println!("Parsed Handshake Two: {:?}", &buffer); + + // Transport Mode + let sess = initiator.into_transport_mode().unwrap(); + let mut state = crypto_state.state.lock().unwrap(); + *state = Some(sess); + } + + let buf = { + let mut sess = crypto_state.state.lock().unwrap(); + let sess = sess.as_mut().unwrap(); + let mut buf = vec![0u8; 65536]; + let len = sess.write_message(message.payload.as_slice(), &mut buf).unwrap(); + buf = buf[..len].to_vec(); + buf + }; + + let msg = OutgoingMessage { + payload: BitwardenCryptoProtocolFrame::noise_frame(BitwardenNoiseFrame::Payload { payload: buf }).as_cbor(), + destination: message.destination.clone(), + topic: message.topic, + }; + println!("Sending payload: {:?}", msg); + communication.send(msg).await.map_err(SendError::Communication)?; + + return Ok(()); + } + + async fn receive( + &self, + communication: &Com, + sessions: &Ses, + ) -> Result> { + let message = communication + .receive() + .await + .map_err(ReceiveError::Communication)?; + let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { + panic!("Session not found"); + }; + let crypto_state = match crypto_state_opt { + Some(state) => state, + None => { + let new_state = NoiseCryptoProviderState { state: Arc::new(Mutex::new(None)) }; + sessions.save(message.destination.clone(), new_state.clone()).await; + new_state + } + }; + + let payload = message.payload; + println!("Received payload: {:?}", payload); + let crypto_protocol_frame: BitwardenCryptoProtocolFrame = ciborium::from_reader(payload.as_slice()).unwrap(); + match crypto_protocol_frame.protocol_identifier { + BitwardenCryptoProtocolIdentifier::NoiseCbor => { + let protocol_frame = BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); + match protocol_frame { + BitwardenNoiseFrame::HandshakeOne { params, payload } => { + let mut responder = if params == "Noise_NN_25519_ChaChaPoly_BLAKE2s" { + snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap()).build_responder().unwrap() + } else { + panic!("Invalid protocol params"); + }; + let mut buffer = vec![0u8; 65536]; + responder.read_message(payload.as_slice(), &mut buffer).unwrap(); + + let res = responder.write_message(&[], &mut buffer).unwrap(); + buffer = buffer[..res].to_vec(); + println!("Handshake Two: {:?}", res); + let handshake_two = BitwardenNoiseFrame::HandshakeTwo { payload: buffer.to_vec() }; + let msg = OutgoingMessage { + payload: BitwardenCryptoProtocolFrame::noise_frame(handshake_two).as_cbor(), + destination: message.destination.clone(), + topic: None, + }; + println!("Sending Handshake Two: {:?}", msg); + let res = communication.send(msg).await; + println!("Sent Handshake Two"); + + { + let mut state = crypto_state.state.lock().unwrap(); + *state = Some(responder.into_transport_mode().unwrap()); + println!("Recv Handshake complete"); + } + + + let payload = communication.receive().await.map_err(ReceiveError::Communication)?; + let crypto_protocol_frame: BitwardenCryptoProtocolFrame = ciborium::from_reader(payload.payload.as_slice()).unwrap(); + let protocol_frame = BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); + let BitwardenNoiseFrame::Payload { payload } = protocol_frame else { + panic!("Expected Payload"); + }; + + // read message + println!("Waiting for payload"); + let mut buf = vec![0u8; 65536]; + let mut sess = crypto_state.state.lock().unwrap(); + let sess = sess.as_mut().unwrap(); + let len = sess.read_message(payload.as_slice(), &mut buf).unwrap(); + buf = buf[..len].to_vec(); + println!("Parsed payload: {:?}", &buf); + return Ok(IncomingMessage { + payload: buf, + destination: message.destination.clone(), + source: message.source.clone(), + topic: message.topic, + }); + }, + BitwardenNoiseFrame::Payload { payload } => { + let mut buf = vec![0u8; 65536]; + let mut sess = crypto_state.state.lock().unwrap(); + let sess = sess.as_mut().unwrap(); + let len = sess.read_message(payload.as_slice(), &mut buf).unwrap(); + buf = buf[..len].to_vec(); + println!("Parsed payload: {:?}", &buf); + return Ok(IncomingMessage { + payload: buf, + destination: message.destination.clone(), + source: message.source.clone(), + topic: message.topic, + }); + }, + _ => { + panic!("Invalid protocol frame"); + } + } + }, + _ => { + todo!() + } + }; + todo!(); + } +} diff --git a/crates/bitwarden-ipc/src/traits/mod.rs b/crates/bitwarden-ipc/src/traits/mod.rs index 73a15a6ad..7a2179594 100644 --- a/crates/bitwarden-ipc/src/traits/mod.rs +++ b/crates/bitwarden-ipc/src/traits/mod.rs @@ -1,5 +1,6 @@ mod communication_backend; mod crypto_provider; +mod crypto_provider_impl; mod session_repository; #[cfg(test)] @@ -7,3 +8,4 @@ pub use communication_backend::tests; pub use communication_backend::CommunicationBackend; pub use crypto_provider::{CryptoProvider, NoEncryptionCryptoProvider}; pub use session_repository::{InMemorySessionRepository, SessionRepository}; +pub use crypto_provider_impl::BitwardenCryptoProvider; \ No newline at end of file diff --git a/crates/bitwarden-ipc/src/wasm/error.rs b/crates/bitwarden-ipc/src/wasm/error.rs index 44a9703a2..7ca7feee3 100644 --- a/crates/bitwarden-ipc/src/wasm/error.rs +++ b/crates/bitwarden-ipc/src/wasm/error.rs @@ -33,6 +33,10 @@ impl From> for JsSendError { crypto: JsValue::UNDEFINED, communication: e, }, + SendError::ReceiveError => JsSendError { + crypto: JsValue::UNDEFINED, + communication: JsValue::UNDEFINED, + }, } } } From c3d7af25dc4b177b5bb299de8b53c361e705b57f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 16:52:33 +0200 Subject: [PATCH 2/5] Cleanup --- crates/bitwarden-ipc/src/error.rs | 2 +- crates/bitwarden-ipc/src/ipc_client.rs | 71 +++-- .../src/traits/communication_backend.rs | 14 +- .../src/traits/crypto_provider.rs | 2 - .../src/traits/crypto_provider_impl.rs | 269 ---------------- crates/bitwarden-ipc/src/traits/mod.rs | 5 +- .../src/traits/noise_crypto_provider.rs | 286 ++++++++++++++++++ 7 files changed, 351 insertions(+), 298 deletions(-) delete mode 100644 crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs create mode 100644 crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs diff --git a/crates/bitwarden-ipc/src/error.rs b/crates/bitwarden-ipc/src/error.rs index d02a23f56..5bd3e35a9 100644 --- a/crates/bitwarden-ipc/src/error.rs +++ b/crates/bitwarden-ipc/src/error.rs @@ -9,7 +9,7 @@ pub enum SendError { Communication(Com), #[error("Receive Error")] - ReceiveError + ReceiveError, } #[derive(Clone, Debug, Error, PartialEq, Eq)] diff --git a/crates/bitwarden-ipc/src/ipc_client.rs b/crates/bitwarden-ipc/src/ipc_client.rs index db8b87ca9..4c955643d 100644 --- a/crates/bitwarden-ipc/src/ipc_client.rs +++ b/crates/bitwarden-ipc/src/ipc_client.rs @@ -102,7 +102,11 @@ mod tests { use crate::{ endpoint::Endpoint, traits::{ - tests::{TestCommunicationBackend, TestCommunicationBackendReceiveError, TestTwoWayCommunicationBackend}, BitwardenCryptoProvider, InMemorySessionRepository, NoEncryptionCryptoProvider + tests::{ + TestCommunicationBackend, TestCommunicationBackendReceiveError, + TestTwoWayCommunicationBackend, + }, + InMemorySessionRepository, NoEncryptionCryptoProvider, NoiseCryptoProvider, }, }; @@ -187,7 +191,7 @@ mod tests { destination: Endpoint::BrowserBackground, topic: None, }; - let crypto_provider = BitwardenCryptoProvider; + let crypto_provider = NoiseCryptoProvider; let communication_provider = TestCommunicationBackend::new(); let session_map = InMemorySessionRepository::new(HashMap::new()); let client = IpcClient::new(crypto_provider, communication_provider.clone(), session_map); @@ -374,31 +378,58 @@ mod tests { let (sender_communication_provider, receiver_communication_provider) = TestTwoWayCommunicationBackend::new(); - // start thread let a = tokio::spawn(async move { - let receiver_crypto_provider = BitwardenCryptoProvider; + let receiver_crypto_provider = NoiseCryptoProvider; let receiver_session_map = InMemorySessionRepository::new(HashMap::new()); - let receiver_client = IpcClient::new(receiver_crypto_provider, receiver_communication_provider.clone(), receiver_session_map); - let received_message = receiver_client.receive(None, None).await.unwrap(); - println!("Received message: {:?}", String::from_utf8(received_message.payload)); + let receiver_client = IpcClient::new( + receiver_crypto_provider, + receiver_communication_provider.clone(), + receiver_session_map, + ); + + for i in 0..10 { + let recv_message = receiver_client.receive(None, None).await.unwrap(); + println!( + "A: Received Message {:?}", + String::from_utf8(recv_message.payload.clone()) + ); + let message = OutgoingMessage { + payload: format!("Hello, world! {}", i).as_bytes().to_vec(), + destination: Endpoint::BrowserBackground, + topic: None, + }; + println!("A: Sending Message {:?}", message); + receiver_client.send(message.clone()).await.unwrap(); + } }); let b = tokio::spawn(async move { - let message = OutgoingMessage { - payload: "Hello, world!".as_bytes().to_vec(), - destination: Endpoint::BrowserBackground, - topic: None, - }; - let sender_crypto_provider = BitwardenCryptoProvider; + let sender_crypto_provider = NoiseCryptoProvider; let sender_session_map = InMemorySessionRepository::new(HashMap::new()); - let sender_client = IpcClient::new(sender_crypto_provider, sender_communication_provider.clone(), sender_session_map); - sender_client.send(message.clone()).await.unwrap(); - //sender_client.send(message.clone()).await.unwrap(); + let sender_client = IpcClient::new( + sender_crypto_provider, + sender_communication_provider.clone(), + sender_session_map, + ); + + for i in 0..10 { + let message = OutgoingMessage { + payload: format!("Hello, world! {}", i).as_bytes().to_vec(), + destination: Endpoint::BrowserBackground, + topic: None, + }; + println!("B: Sending Message {:?}", message); + sender_client.send(message.clone()).await.unwrap(); + + let recv_message = sender_client.receive(None, None).await.unwrap(); + println!( + "B: Received Message {:?}", + String::from_utf8(recv_message.payload.clone()) + ); + assert_eq!(recv_message.payload, message.payload); + } }); - // sleep - tokio::time::sleep(Duration::from_secs(3)).await; - println!("Waiting for tasks to finish..."); - let _ = tokio::try_join!(a, b); + let _ = tokio::join!(a, b); } } diff --git a/crates/bitwarden-ipc/src/traits/communication_backend.rs b/crates/bitwarden-ipc/src/traits/communication_backend.rs index 132ad7b8e..6a8fe1b7e 100644 --- a/crates/bitwarden-ipc/src/traits/communication_backend.rs +++ b/crates/bitwarden-ipc/src/traits/communication_backend.rs @@ -26,7 +26,10 @@ pub mod tests { use std::{collections::VecDeque, rc::Rc, sync::Arc}; use thiserror::Error; - use tokio::sync::{mpsc::{self, Receiver, Sender}, Mutex, RwLock}; + use tokio::sync::{ + mpsc::{self, Receiver, Sender}, + Mutex, RwLock, + }; use super::*; @@ -115,9 +118,12 @@ pub mod tests { async fn receive(&self) -> Result { let mut receiver = self.incoming.lock().await; let message = receiver.recv().await.unwrap(); - Ok(IncomingMessage { payload:message.payload, destination: message.destination, source: crate::endpoint::Endpoint::DesktopRenderer, topic: None }) + Ok(IncomingMessage { + payload: message.payload, + destination: message.destination, + source: crate::endpoint::Endpoint::DesktopRenderer, + topic: None, + }) } } - - } diff --git a/crates/bitwarden-ipc/src/traits/crypto_provider.rs b/crates/bitwarden-ipc/src/traits/crypto_provider.rs index 8fa9de43a..e1b729954 100644 --- a/crates/bitwarden-ipc/src/traits/crypto_provider.rs +++ b/crates/bitwarden-ipc/src/traits/crypto_provider.rs @@ -1,5 +1,3 @@ -use serde::{Deserialize, Serialize}; - use super::{CommunicationBackend, SessionRepository}; use crate::{ error::{ReceiveError, SendError}, diff --git a/crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs b/crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs deleted file mode 100644 index edf71ac28..000000000 --- a/crates/bitwarden-ipc/src/traits/crypto_provider_impl.rs +++ /dev/null @@ -1,269 +0,0 @@ -use core::panic; -use std::{sync::{Arc, Mutex}, vec}; - -use serde::{Deserialize, Serialize}; -use snow::TransportState; - -use crate::{error::{ReceiveError, SendError}, message::{IncomingMessage, OutgoingMessage}}; - -use super::{CommunicationBackend, CryptoProvider, SessionRepository}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -enum BitwardenCryptoProtocolIdentifier { - NoiseCbor, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -struct BitwardenCryptoProtocolFrame { - protocol_identifier: BitwardenCryptoProtocolIdentifier, - protocol_frame: Vec, -} - -impl BitwardenCryptoProtocolFrame { - fn noise_frame(frame: BitwardenNoiseFrame) -> Self { - BitwardenCryptoProtocolFrame { - protocol_identifier: BitwardenCryptoProtocolIdentifier::NoiseCbor, - protocol_frame: frame.as_cbor(), - } - } - - fn as_cbor(&self) -> Vec { - let mut buffer = Vec::new(); - ciborium::into_writer(self, &mut buffer).unwrap(); - buffer - } - - fn from_cbor(buffer: &[u8]) -> Self { - ciborium::from_reader(buffer).unwrap() - } -} - - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum BitwardenNoiseFrame { - HandshakeOne { - params: String, - payload: Vec, - }, - HandshakeTwo { - payload: Vec, - }, - HandshakeThree { - payload: Vec, - }, - Payload { - payload: Vec, - } -} - -impl BitwardenNoiseFrame { - fn as_cbor(&self) -> Vec { - let mut buffer = Vec::new(); - ciborium::into_writer(self, &mut buffer).unwrap(); - buffer - } - - fn from_cbor(buffer: &[u8]) -> Self { - ciborium::from_reader(buffer).unwrap() - } -} - -pub struct BitwardenCryptoProvider; -#[derive(Clone, Debug)] -pub struct NoiseCryptoProviderState { - state: Arc>>, -} - -impl CryptoProvider for BitwardenCryptoProvider -where - Com: CommunicationBackend, - Ses: SessionRepository, -{ - type Session = NoiseCryptoProviderState; - type SendError = Com::SendError; - type ReceiveError = Com::ReceiveError; - - async fn send( - &self, - communication: &Com, - sessions: &Ses, - message: OutgoingMessage, - ) -> Result<(), SendError> { - let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { - panic!("Session not found"); - }; - let crypto_state = match crypto_state_opt { - Some(state) => state, - None => { - let new_state = NoiseCryptoProviderState { state: Arc::new(Mutex::new(None)) }; - sessions.save(message.destination.clone(), new_state.clone()).await; - new_state - } - }; - - // Session is not established yet. Establish it. - if crypto_state.state.lock().unwrap().is_none() { - let mut initiator = snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap()).build_initiator().unwrap(); - - // Send Handshake One - let mut buffer = vec![0u8; 65536]; - let res = initiator.write_message(&[], &mut buffer).unwrap(); - buffer = buffer[..res].to_vec(); - println!("Handshake One: {:?}", res); - let handshake_one = BitwardenNoiseFrame::HandshakeOne { params: "Noise_NN_25519_ChaChaPoly_BLAKE2s".to_string(), payload: buffer.to_vec() }; - let msg = OutgoingMessage { - payload: BitwardenCryptoProtocolFrame::noise_frame(handshake_one).as_cbor(), - destination: message.destination.clone(), - topic: None, - }; - println!("Sending Handshake One: {:?}", msg); - communication.send(msg).await.map_err(SendError::Communication)?; - println!("Sent Handshake One"); - - // sleep - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - - // Receive Handshake Two - let mut buffer = Vec::new(); - println!("Waiting for Handshake Two"); - let resp = communication.receive().await.map_err(|_| SendError::ReceiveError)?; - let handshake_two = BitwardenCryptoProtocolFrame::from_cbor(resp.payload.as_slice()); - let resp = BitwardenNoiseFrame::from_cbor(handshake_two.protocol_frame.as_slice()); - let BitwardenNoiseFrame::HandshakeTwo { payload } = resp else { - panic!("Expected Handshake Two"); - }; - println!("Received Handshake Two: {:?}", &payload); - initiator.read_message(&payload, &mut buffer).unwrap(); - println!("Parsed Handshake Two: {:?}", &buffer); - - // Transport Mode - let sess = initiator.into_transport_mode().unwrap(); - let mut state = crypto_state.state.lock().unwrap(); - *state = Some(sess); - } - - let buf = { - let mut sess = crypto_state.state.lock().unwrap(); - let sess = sess.as_mut().unwrap(); - let mut buf = vec![0u8; 65536]; - let len = sess.write_message(message.payload.as_slice(), &mut buf).unwrap(); - buf = buf[..len].to_vec(); - buf - }; - - let msg = OutgoingMessage { - payload: BitwardenCryptoProtocolFrame::noise_frame(BitwardenNoiseFrame::Payload { payload: buf }).as_cbor(), - destination: message.destination.clone(), - topic: message.topic, - }; - println!("Sending payload: {:?}", msg); - communication.send(msg).await.map_err(SendError::Communication)?; - - return Ok(()); - } - - async fn receive( - &self, - communication: &Com, - sessions: &Ses, - ) -> Result> { - let message = communication - .receive() - .await - .map_err(ReceiveError::Communication)?; - let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { - panic!("Session not found"); - }; - let crypto_state = match crypto_state_opt { - Some(state) => state, - None => { - let new_state = NoiseCryptoProviderState { state: Arc::new(Mutex::new(None)) }; - sessions.save(message.destination.clone(), new_state.clone()).await; - new_state - } - }; - - let payload = message.payload; - println!("Received payload: {:?}", payload); - let crypto_protocol_frame: BitwardenCryptoProtocolFrame = ciborium::from_reader(payload.as_slice()).unwrap(); - match crypto_protocol_frame.protocol_identifier { - BitwardenCryptoProtocolIdentifier::NoiseCbor => { - let protocol_frame = BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); - match protocol_frame { - BitwardenNoiseFrame::HandshakeOne { params, payload } => { - let mut responder = if params == "Noise_NN_25519_ChaChaPoly_BLAKE2s" { - snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap()).build_responder().unwrap() - } else { - panic!("Invalid protocol params"); - }; - let mut buffer = vec![0u8; 65536]; - responder.read_message(payload.as_slice(), &mut buffer).unwrap(); - - let res = responder.write_message(&[], &mut buffer).unwrap(); - buffer = buffer[..res].to_vec(); - println!("Handshake Two: {:?}", res); - let handshake_two = BitwardenNoiseFrame::HandshakeTwo { payload: buffer.to_vec() }; - let msg = OutgoingMessage { - payload: BitwardenCryptoProtocolFrame::noise_frame(handshake_two).as_cbor(), - destination: message.destination.clone(), - topic: None, - }; - println!("Sending Handshake Two: {:?}", msg); - let res = communication.send(msg).await; - println!("Sent Handshake Two"); - - { - let mut state = crypto_state.state.lock().unwrap(); - *state = Some(responder.into_transport_mode().unwrap()); - println!("Recv Handshake complete"); - } - - - let payload = communication.receive().await.map_err(ReceiveError::Communication)?; - let crypto_protocol_frame: BitwardenCryptoProtocolFrame = ciborium::from_reader(payload.payload.as_slice()).unwrap(); - let protocol_frame = BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); - let BitwardenNoiseFrame::Payload { payload } = protocol_frame else { - panic!("Expected Payload"); - }; - - // read message - println!("Waiting for payload"); - let mut buf = vec![0u8; 65536]; - let mut sess = crypto_state.state.lock().unwrap(); - let sess = sess.as_mut().unwrap(); - let len = sess.read_message(payload.as_slice(), &mut buf).unwrap(); - buf = buf[..len].to_vec(); - println!("Parsed payload: {:?}", &buf); - return Ok(IncomingMessage { - payload: buf, - destination: message.destination.clone(), - source: message.source.clone(), - topic: message.topic, - }); - }, - BitwardenNoiseFrame::Payload { payload } => { - let mut buf = vec![0u8; 65536]; - let mut sess = crypto_state.state.lock().unwrap(); - let sess = sess.as_mut().unwrap(); - let len = sess.read_message(payload.as_slice(), &mut buf).unwrap(); - buf = buf[..len].to_vec(); - println!("Parsed payload: {:?}", &buf); - return Ok(IncomingMessage { - payload: buf, - destination: message.destination.clone(), - source: message.source.clone(), - topic: message.topic, - }); - }, - _ => { - panic!("Invalid protocol frame"); - } - } - }, - _ => { - todo!() - } - }; - todo!(); - } -} diff --git a/crates/bitwarden-ipc/src/traits/mod.rs b/crates/bitwarden-ipc/src/traits/mod.rs index 7a2179594..cc0572fdc 100644 --- a/crates/bitwarden-ipc/src/traits/mod.rs +++ b/crates/bitwarden-ipc/src/traits/mod.rs @@ -1,11 +1,12 @@ mod communication_backend; mod crypto_provider; -mod crypto_provider_impl; +mod noise_crypto_provider; mod session_repository; #[cfg(test)] pub use communication_backend::tests; +#[cfg(test)] +pub use noise_crypto_provider::NoiseCryptoProvider; pub use communication_backend::CommunicationBackend; pub use crypto_provider::{CryptoProvider, NoEncryptionCryptoProvider}; pub use session_repository::{InMemorySessionRepository, SessionRepository}; -pub use crypto_provider_impl::BitwardenCryptoProvider; \ No newline at end of file diff --git a/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs b/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs new file mode 100644 index 000000000..d4e506ec3 --- /dev/null +++ b/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs @@ -0,0 +1,286 @@ +use core::panic; +use std::{ + sync::{Arc, Mutex}, + vec, +}; + +use serde::{Deserialize, Serialize}; +use snow::TransportState; + +use super::{CommunicationBackend, CryptoProvider, SessionRepository}; +use crate::{ + error::{ReceiveError, SendError}, + message::{IncomingMessage, OutgoingMessage}, +}; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +enum BitwardenCryptoProtocolIdentifier { + Noise, +} + +/// The Bitwarden IPC protocol is can have different crypto protocols. +/// Currently there is exactly one - Noise - implemented. +#[derive(Clone, Debug, Deserialize, Serialize)] +struct BitwardenIpcCryptoProtocolFrame { + protocol_identifier: BitwardenCryptoProtocolIdentifier, + protocol_frame: Vec, +} + +impl BitwardenIpcCryptoProtocolFrame { + fn as_cbor(&self) -> Vec { + let mut buffer = Vec::new(); + ciborium::into_writer(self, &mut buffer).unwrap(); + buffer + } + + fn from_cbor(buffer: &[u8]) -> Self { + ciborium::from_reader(buffer).unwrap() + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum BitwardenNoiseFrame { + HandshakeStart { + ciphersuite: String, + payload: Vec, + }, + HandshakeFinish { + payload: Vec, + }, + Payload { + payload: Vec, + }, +} + +impl BitwardenNoiseFrame { + fn as_cbor(&self) -> Vec { + let mut buffer = Vec::new(); + ciborium::into_writer(self, &mut buffer).unwrap(); + buffer + } + + fn from_cbor(buffer: &[u8]) -> Self { + ciborium::from_reader(buffer).unwrap() + } + + fn to_crypto_protocol_frame(&self) -> BitwardenIpcCryptoProtocolFrame { + BitwardenIpcCryptoProtocolFrame { + protocol_identifier: BitwardenCryptoProtocolIdentifier::Noise, + protocol_frame: self.as_cbor(), + } + } +} + +pub struct NoiseCryptoProvider; +#[derive(Clone, Debug)] +pub struct NoiseCryptoProviderState { + state: Arc>>, +} + +impl CryptoProvider for NoiseCryptoProvider +where + Com: CommunicationBackend, + Ses: SessionRepository, +{ + type Session = NoiseCryptoProviderState; + type SendError = Com::SendError; + type ReceiveError = Com::ReceiveError; + + async fn send( + &self, + communication: &Com, + sessions: &Ses, + message: OutgoingMessage, + ) -> Result<(), SendError> { + let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { + panic!("Session not found"); + }; + let crypto_state = match crypto_state_opt { + Some(state) => state, + None => { + let new_state = NoiseCryptoProviderState { + state: Arc::new(Mutex::new(None)), + }; + sessions + .save(message.destination.clone(), new_state.clone()) + .await; + new_state + } + }; + + // Session is not established yet. Establish it. + if crypto_state.state.lock().unwrap().is_none() { + let cipher_suite = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; + let mut initiator = snow::Builder::new(cipher_suite.parse().unwrap()) + .build_initiator() + .unwrap(); + + // Send Handshake One + let handshake_start_message = OutgoingMessage { + payload: BitwardenNoiseFrame::HandshakeStart { + ciphersuite: cipher_suite.to_string(), + payload: { + let mut buffer = vec![0u8; 65536]; + let res = initiator.write_message(&[], &mut buffer).unwrap(); + buffer[..res].to_vec() + }, + } + .to_crypto_protocol_frame() + .as_cbor(), + destination: message.destination.clone(), + topic: None, + }; + communication + .send(handshake_start_message) + .await + .map_err(SendError::Communication)?; + + // Receive Handshake Two + let handshake_finish_frame = communication + .receive() + .await + .map_err(|_| SendError::ReceiveError) + .map(|message| { + BitwardenIpcCryptoProtocolFrame::from_cbor(message.payload.as_slice()) + }) + .map(|frame| { + BitwardenNoiseFrame::from_cbor(frame.protocol_frame.as_slice()) + })?; + let BitwardenNoiseFrame::HandshakeFinish { payload } = handshake_finish_frame else { + panic!("Expected Handshake Two"); + }; + initiator.read_message(&payload, &mut Vec::new()).unwrap(); + + let transport_state = initiator.into_transport_mode().unwrap(); + let mut state = crypto_state.state.lock().unwrap(); + *state = Some(transport_state); + } + + // Send the payload + let payload_message = OutgoingMessage { + payload: BitwardenNoiseFrame::Payload { + payload: { + let mut transport_state = crypto_state.state.lock().unwrap(); + let transport_state = transport_state.as_mut().unwrap(); + let mut buf = vec![0u8; 65536]; + let len = transport_state + .write_message(message.payload.as_slice(), &mut buf) + .unwrap(); + buf = buf[..len].to_vec(); + println!("Send payload: {:?}", buf); + buf + } + } + .to_crypto_protocol_frame() + .as_cbor(), + destination: message.destination.clone(), + topic: message.topic, + }; + communication + .send(payload_message) + .await + .map_err(SendError::Communication)?; + + return Ok(()); + } + + async fn receive( + &self, + communication: &Com, + sessions: &Ses, + ) -> Result> { + let mut message = communication + .receive() + .await + .map_err(ReceiveError::Communication)?; + let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { + panic!("Session not found"); + }; + let crypto_state = match crypto_state_opt { + Some(state) => state, + None => { + let new_state = NoiseCryptoProviderState { + state: Arc::new(Mutex::new(None)), + }; + sessions + .save(message.destination.clone(), new_state.clone()) + .await; + new_state + } + }; + + let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload); + if crypto_protocol_frame.protocol_identifier + != BitwardenCryptoProtocolIdentifier::Noise + { + panic!("Invalid protocol identifier"); + } + + // Check if session is established + if crypto_state.state.lock().unwrap().is_none() { + let protocol_frame = + BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); + match protocol_frame { + BitwardenNoiseFrame::HandshakeStart { ciphersuite, payload } => { + let supported_ciphersuite = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; + let mut responder = if ciphersuite == supported_ciphersuite { + snow::Builder::new(supported_ciphersuite.parse().unwrap()) + .build_responder() + .unwrap() + } else { + panic!("Invalid protocol params"); + }; + + responder + .read_message(payload.as_slice(), &mut Vec::new()) + .unwrap(); + + let handshake_finish_message = OutgoingMessage { + payload: BitwardenNoiseFrame::HandshakeFinish { + payload: { + let mut buffer = vec![0u8; 65536]; + let res = responder.write_message(&[], &mut buffer).unwrap(); + buffer[..res].to_vec() + } + }.to_crypto_protocol_frame().as_cbor(), + destination: message.destination.clone(), + topic: None, + }; + let res = communication.send(handshake_finish_message).await; + { + let mut transport_state = crypto_state.state.lock().unwrap(); + *transport_state = Some(responder.into_transport_mode().unwrap()); + } + + message = communication.receive().await.map_err(|e| { + ReceiveError::Communication(e) + })?; + } + _ => { + panic!("Invalid protocol frame"); + } + } + } + // Session is established. Read the payload. + let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload); + let protocol_frame = BitwardenNoiseFrame::from_cbor( + crypto_protocol_frame.protocol_frame.as_slice(), + ); + let BitwardenNoiseFrame::Payload { payload } = protocol_frame else { + panic!("Expected Payload"); + }; + + let mut transport_state = crypto_state.state.lock().unwrap(); + let transport_state = transport_state.as_mut().unwrap(); + return Ok(IncomingMessage { + payload: { + let mut buf = vec![0u8; 65536]; + let len = transport_state.read_message(payload.as_slice(), &mut buf).unwrap(); + buf[..len].to_vec() + }, + destination: message.destination.clone(), + source: message.source.clone(), + topic: message.topic, + }); + } +} From 7e1d2589dddd0f4589e3690004df527a00bd68ab Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 16:56:57 +0200 Subject: [PATCH 3/5] Cargo fmt --- crates/bitwarden-ipc/src/ipc_client.rs | 4 +- crates/bitwarden-ipc/src/traits/mod.rs | 4 +- .../src/traits/noise_crypto_provider.rs | 43 ++++++++++--------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/crates/bitwarden-ipc/src/ipc_client.rs b/crates/bitwarden-ipc/src/ipc_client.rs index 4c955643d..a23865298 100644 --- a/crates/bitwarden-ipc/src/ipc_client.rs +++ b/crates/bitwarden-ipc/src/ipc_client.rs @@ -387,7 +387,7 @@ mod tests { receiver_session_map, ); - for i in 0..10 { + for i in 0..10 { let recv_message = receiver_client.receive(None, None).await.unwrap(); println!( "A: Received Message {:?}", @@ -420,7 +420,7 @@ mod tests { }; println!("B: Sending Message {:?}", message); sender_client.send(message.clone()).await.unwrap(); - + let recv_message = sender_client.receive(None, None).await.unwrap(); println!( "B: Received Message {:?}", diff --git a/crates/bitwarden-ipc/src/traits/mod.rs b/crates/bitwarden-ipc/src/traits/mod.rs index cc0572fdc..628fb3eac 100644 --- a/crates/bitwarden-ipc/src/traits/mod.rs +++ b/crates/bitwarden-ipc/src/traits/mod.rs @@ -5,8 +5,8 @@ mod session_repository; #[cfg(test)] pub use communication_backend::tests; -#[cfg(test)] -pub use noise_crypto_provider::NoiseCryptoProvider; pub use communication_backend::CommunicationBackend; pub use crypto_provider::{CryptoProvider, NoEncryptionCryptoProvider}; +#[cfg(test)] +pub use noise_crypto_provider::NoiseCryptoProvider; pub use session_repository::{InMemorySessionRepository, SessionRepository}; diff --git a/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs b/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs index d4e506ec3..36a99e2dc 100644 --- a/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs +++ b/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs @@ -143,9 +143,7 @@ where .map(|message| { BitwardenIpcCryptoProtocolFrame::from_cbor(message.payload.as_slice()) }) - .map(|frame| { - BitwardenNoiseFrame::from_cbor(frame.protocol_frame.as_slice()) - })?; + .map(|frame| BitwardenNoiseFrame::from_cbor(frame.protocol_frame.as_slice()))?; let BitwardenNoiseFrame::HandshakeFinish { payload } = handshake_finish_frame else { panic!("Expected Handshake Two"); }; @@ -169,10 +167,10 @@ where buf = buf[..len].to_vec(); println!("Send payload: {:?}", buf); buf - } + }, } - .to_crypto_protocol_frame() - .as_cbor(), + .to_crypto_protocol_frame() + .as_cbor(), destination: message.destination.clone(), topic: message.topic, }; @@ -210,18 +208,19 @@ where }; let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload); - if crypto_protocol_frame.protocol_identifier - != BitwardenCryptoProtocolIdentifier::Noise - { + if crypto_protocol_frame.protocol_identifier != BitwardenCryptoProtocolIdentifier::Noise { panic!("Invalid protocol identifier"); } // Check if session is established - if crypto_state.state.lock().unwrap().is_none() { + if crypto_state.state.lock().unwrap().is_none() { let protocol_frame = BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); match protocol_frame { - BitwardenNoiseFrame::HandshakeStart { ciphersuite, payload } => { + BitwardenNoiseFrame::HandshakeStart { + ciphersuite, + payload, + } => { let supported_ciphersuite = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; let mut responder = if ciphersuite == supported_ciphersuite { snow::Builder::new(supported_ciphersuite.parse().unwrap()) @@ -241,8 +240,10 @@ where let mut buffer = vec![0u8; 65536]; let res = responder.write_message(&[], &mut buffer).unwrap(); buffer[..res].to_vec() - } - }.to_crypto_protocol_frame().as_cbor(), + }, + } + .to_crypto_protocol_frame() + .as_cbor(), destination: message.destination.clone(), topic: None, }; @@ -252,9 +253,10 @@ where *transport_state = Some(responder.into_transport_mode().unwrap()); } - message = communication.receive().await.map_err(|e| { - ReceiveError::Communication(e) - })?; + message = communication + .receive() + .await + .map_err(|e| ReceiveError::Communication(e))?; } _ => { panic!("Invalid protocol frame"); @@ -263,9 +265,8 @@ where } // Session is established. Read the payload. let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload); - let protocol_frame = BitwardenNoiseFrame::from_cbor( - crypto_protocol_frame.protocol_frame.as_slice(), - ); + let protocol_frame = + BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); let BitwardenNoiseFrame::Payload { payload } = protocol_frame else { panic!("Expected Payload"); }; @@ -275,7 +276,9 @@ where return Ok(IncomingMessage { payload: { let mut buf = vec![0u8; 65536]; - let len = transport_state.read_message(payload.as_slice(), &mut buf).unwrap(); + let len = transport_state + .read_message(payload.as_slice(), &mut buf) + .unwrap(); buf[..len].to_vec() }, destination: message.destination.clone(), From 2a7c337471e8828dc25cfee403e1cf7302827d82 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 17:39:09 +0200 Subject: [PATCH 4/5] Further cleanups --- crates/bitwarden-ipc/src/error.rs | 13 +- .../src/traits/communication_backend.rs | 6 +- .../src/traits/noise_crypto_provider.rs | 134 ++++++++++++------ crates/bitwarden-ipc/src/wasm/error.rs | 12 +- 4 files changed, 114 insertions(+), 51 deletions(-) diff --git a/crates/bitwarden-ipc/src/error.rs b/crates/bitwarden-ipc/src/error.rs index 5bd3e35a9..a45465f1b 100644 --- a/crates/bitwarden-ipc/src/error.rs +++ b/crates/bitwarden-ipc/src/error.rs @@ -8,8 +8,8 @@ pub enum SendError { #[error("Communication error: {0}")] Communication(Com), - #[error("Receive Error")] - ReceiveError, + #[error("Handshake error")] + HandshakeError, } #[derive(Clone, Debug, Error, PartialEq, Eq)] @@ -22,6 +22,12 @@ pub enum ReceiveError { #[error("Communication error: {0}")] Communication(Com), + + #[error("Handshake error")] + HandshakeError, + + #[error("Decode Error")] + DecodeError, } #[derive(Clone, Debug, Error, PartialEq, Eq)] @@ -47,6 +53,9 @@ impl From> ReceiveError::Timeout => TypedReceiveError::Timeout, ReceiveError::Crypto(crypto) => TypedReceiveError::Crypto(crypto), ReceiveError::Communication(com) => TypedReceiveError::Communication(com), + // todo + ReceiveError::HandshakeError => TypedReceiveError::Timeout, + ReceiveError::DecodeError => TypedReceiveError::Timeout, } } } diff --git a/crates/bitwarden-ipc/src/traits/communication_backend.rs b/crates/bitwarden-ipc/src/traits/communication_backend.rs index 6a8fe1b7e..c4e5098c1 100644 --- a/crates/bitwarden-ipc/src/traits/communication_backend.rs +++ b/crates/bitwarden-ipc/src/traits/communication_backend.rs @@ -92,15 +92,15 @@ pub mod tests { impl TestTwoWayCommunicationBackend { pub fn new() -> (Self, Self) { - let (outgoing, incoming) = mpsc::channel(10); + let (outgoing0, incoming0) = mpsc::channel(10); let (outgoing1, incoming1) = mpsc::channel(10); let one = TestTwoWayCommunicationBackend { - outgoing: outgoing, + outgoing: outgoing0, incoming: Arc::new(Mutex::new(incoming1)), }; let two = TestTwoWayCommunicationBackend { outgoing: outgoing1, - incoming: Arc::new(Mutex::new(incoming)), + incoming: Arc::new(Mutex::new(incoming0)), }; (one, two) } diff --git a/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs b/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs index 36a99e2dc..5f7b64f92 100644 --- a/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs +++ b/crates/bitwarden-ipc/src/traits/noise_crypto_provider.rs @@ -29,12 +29,13 @@ struct BitwardenIpcCryptoProtocolFrame { impl BitwardenIpcCryptoProtocolFrame { fn as_cbor(&self) -> Vec { let mut buffer = Vec::new(); + #[allow(clippy::unwrap_used)] ciborium::into_writer(self, &mut buffer).unwrap(); buffer } - fn from_cbor(buffer: &[u8]) -> Self { - ciborium::from_reader(buffer).unwrap() + fn from_cbor(buffer: &[u8]) -> Result { + ciborium::from_reader(buffer).map_err(|_| ()) } } @@ -55,12 +56,13 @@ pub enum BitwardenNoiseFrame { impl BitwardenNoiseFrame { fn as_cbor(&self) -> Vec { let mut buffer = Vec::new(); + #[allow(clippy::unwrap_used)] ciborium::into_writer(self, &mut buffer).unwrap(); buffer } - fn from_cbor(buffer: &[u8]) -> Self { - ciborium::from_reader(buffer).unwrap() + fn from_cbor(buffer: &[u8]) -> Result { + ciborium::from_reader(buffer).map_err(|_| ()) } fn to_crypto_protocol_frame(&self) -> BitwardenIpcCryptoProtocolFrame { @@ -92,7 +94,7 @@ where sessions: &Ses, message: OutgoingMessage, ) -> Result<(), SendError> { - let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { + let Ok(crypto_state_opt) = sessions.get(message.destination).await else { panic!("Session not found"); }; let crypto_state = match crypto_state_opt { @@ -101,19 +103,26 @@ where let new_state = NoiseCryptoProviderState { state: Arc::new(Mutex::new(None)), }; + // todo sessions - .save(message.destination.clone(), new_state.clone()) - .await; + .save(message.destination, new_state.clone()) + .await + .map_err(|_| SendError::HandshakeError)?; new_state } }; // Session is not established yet. Establish it. + #[allow(clippy::unwrap_used)] if crypto_state.state.lock().unwrap().is_none() { let cipher_suite = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; - let mut initiator = snow::Builder::new(cipher_suite.parse().unwrap()) - .build_initiator() - .unwrap(); + let mut initiator = snow::Builder::new( + cipher_suite + .parse() + .map_err(|_| SendError::HandshakeError)?, + ) + .build_initiator() + .unwrap(); // Send Handshake One let handshake_start_message = OutgoingMessage { @@ -121,13 +130,15 @@ where ciphersuite: cipher_suite.to_string(), payload: { let mut buffer = vec![0u8; 65536]; - let res = initiator.write_message(&[], &mut buffer).unwrap(); + let res = initiator + .write_message(&[], &mut buffer) + .map_err(|_| SendError::HandshakeError)?; buffer[..res].to_vec() }, } .to_crypto_protocol_frame() .as_cbor(), - destination: message.destination.clone(), + destination: message.destination, topic: None, }; communication @@ -136,21 +147,29 @@ where .map_err(SendError::Communication)?; // Receive Handshake Two - let handshake_finish_frame = communication + let message = communication .receive() .await - .map_err(|_| SendError::ReceiveError) - .map(|message| { - BitwardenIpcCryptoProtocolFrame::from_cbor(message.payload.as_slice()) - }) - .map(|frame| BitwardenNoiseFrame::from_cbor(frame.protocol_frame.as_slice()))?; + .map_err(|_| SendError::HandshakeError)?; + let frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload) + .map_err(|_| SendError::HandshakeError)?; + let handshake_finish_frame = + BitwardenNoiseFrame::from_cbor(frame.protocol_frame.as_slice()) + .map_err(|_| SendError::HandshakeError)?; let BitwardenNoiseFrame::HandshakeFinish { payload } = handshake_finish_frame else { panic!("Expected Handshake Two"); }; - initiator.read_message(&payload, &mut Vec::new()).unwrap(); + initiator + .read_message(&payload, &mut Vec::new()) + .map_err(|_| SendError::HandshakeError)?; - let transport_state = initiator.into_transport_mode().unwrap(); - let mut state = crypto_state.state.lock().unwrap(); + let transport_state = initiator + .into_transport_mode() + .map_err(|_| SendError::HandshakeError)?; + let mut state = crypto_state + .state + .lock() + .map_err(|_| SendError::HandshakeError)?; *state = Some(transport_state); } @@ -158,12 +177,15 @@ where let payload_message = OutgoingMessage { payload: BitwardenNoiseFrame::Payload { payload: { + #[allow(clippy::unwrap_used)] let mut transport_state = crypto_state.state.lock().unwrap(); - let transport_state = transport_state.as_mut().unwrap(); + // todo error type + let transport_state = + transport_state.as_mut().ok_or(SendError::HandshakeError)?; let mut buf = vec![0u8; 65536]; let len = transport_state .write_message(message.payload.as_slice(), &mut buf) - .unwrap(); + .map_err(|_| SendError::HandshakeError)?; buf = buf[..len].to_vec(); println!("Send payload: {:?}", buf); buf @@ -171,7 +193,7 @@ where } .to_crypto_protocol_frame() .as_cbor(), - destination: message.destination.clone(), + destination: message.destination, topic: message.topic, }; communication @@ -179,7 +201,7 @@ where .await .map_err(SendError::Communication)?; - return Ok(()); + Ok(()) } async fn receive( @@ -191,7 +213,7 @@ where .receive() .await .map_err(ReceiveError::Communication)?; - let Ok(crypto_state_opt) = sessions.get(message.destination.clone()).await else { + let Ok(crypto_state_opt) = sessions.get(message.destination).await else { panic!("Session not found"); }; let crypto_state = match crypto_state_opt { @@ -201,21 +223,26 @@ where state: Arc::new(Mutex::new(None)), }; sessions - .save(message.destination.clone(), new_state.clone()) - .await; + .save(message.destination, new_state.clone()) + .await + // todo + .map_err(|_| ReceiveError::HandshakeError)?; new_state } }; - let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload); + let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload) + .map_err(|_| ReceiveError::DecodeError)?; if crypto_protocol_frame.protocol_identifier != BitwardenCryptoProtocolIdentifier::Noise { panic!("Invalid protocol identifier"); } // Check if session is established + #[allow(clippy::unwrap_used)] if crypto_state.state.lock().unwrap().is_none() { let protocol_frame = - BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); + BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()) + .map_err(|_| ReceiveError::DecodeError)?; match protocol_frame { BitwardenNoiseFrame::HandshakeStart { ciphersuite, @@ -223,9 +250,13 @@ where } => { let supported_ciphersuite = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; let mut responder = if ciphersuite == supported_ciphersuite { - snow::Builder::new(supported_ciphersuite.parse().unwrap()) - .build_responder() - .unwrap() + snow::Builder::new( + supported_ciphersuite + .parse() + .map_err(|_| ReceiveError::HandshakeError)?, + ) + .build_responder() + .unwrap() } else { panic!("Invalid protocol params"); }; @@ -238,25 +269,34 @@ where payload: BitwardenNoiseFrame::HandshakeFinish { payload: { let mut buffer = vec![0u8; 65536]; - let res = responder.write_message(&[], &mut buffer).unwrap(); + let res = responder + .write_message(&[], &mut buffer) + .map_err(|_| ReceiveError::HandshakeError)?; buffer[..res].to_vec() }, } .to_crypto_protocol_frame() .as_cbor(), - destination: message.destination.clone(), + destination: message.destination, topic: None, }; - let res = communication.send(handshake_finish_message).await; + communication + .send(handshake_finish_message) + .await + .map_err(|_| ReceiveError::HandshakeError)?; { let mut transport_state = crypto_state.state.lock().unwrap(); - *transport_state = Some(responder.into_transport_mode().unwrap()); + *transport_state = Some( + responder + .into_transport_mode() + .map_err(|_| ReceiveError::HandshakeError)?, + ); } message = communication .receive() .await - .map_err(|e| ReceiveError::Communication(e))?; + .map_err(ReceiveError::Communication)?; } _ => { panic!("Invalid protocol frame"); @@ -264,26 +304,30 @@ where } } // Session is established. Read the payload. - let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload); + let crypto_protocol_frame = BitwardenIpcCryptoProtocolFrame::from_cbor(&message.payload) + .map_err(|_| ReceiveError::DecodeError)?; let protocol_frame = - BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()); + BitwardenNoiseFrame::from_cbor(crypto_protocol_frame.protocol_frame.as_slice()) + .map_err(|_| ReceiveError::DecodeError)?; let BitwardenNoiseFrame::Payload { payload } = protocol_frame else { panic!("Expected Payload"); }; + #[allow(clippy::unwrap_used)] let mut transport_state = crypto_state.state.lock().unwrap(); + #[allow(clippy::unwrap_used)] let transport_state = transport_state.as_mut().unwrap(); - return Ok(IncomingMessage { + Ok(IncomingMessage { payload: { let mut buf = vec![0u8; 65536]; let len = transport_state .read_message(payload.as_slice(), &mut buf) - .unwrap(); + .map_err(|_| ReceiveError::DecodeError)?; buf[..len].to_vec() }, - destination: message.destination.clone(), - source: message.source.clone(), + destination: message.destination, + source: message.source, topic: message.topic, - }); + }) } } diff --git a/crates/bitwarden-ipc/src/wasm/error.rs b/crates/bitwarden-ipc/src/wasm/error.rs index 7ca7feee3..3c56ccd49 100644 --- a/crates/bitwarden-ipc/src/wasm/error.rs +++ b/crates/bitwarden-ipc/src/wasm/error.rs @@ -33,7 +33,7 @@ impl From> for JsSendError { crypto: JsValue::UNDEFINED, communication: e, }, - SendError::ReceiveError => JsSendError { + SendError::HandshakeError => JsSendError { crypto: JsValue::UNDEFINED, communication: JsValue::UNDEFINED, }, @@ -59,6 +59,16 @@ impl From> for JsReceiveError { crypto: JsValue::UNDEFINED, communication: e, }, + ReceiveError::HandshakeError => JsReceiveError { + timeout: false, + crypto: JsValue::UNDEFINED, + communication: JsValue::UNDEFINED, + }, + ReceiveError::DecodeError => JsReceiveError { + timeout: false, + crypto: JsValue::UNDEFINED, + communication: JsValue::UNDEFINED, + }, } } } From 14b8da50dc0d5eeb567d386f64c92a7e1ee7d162 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 17:42:29 +0200 Subject: [PATCH 5/5] Undo change --- crates/bitwarden-ipc/src/ipc_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-ipc/src/ipc_client.rs b/crates/bitwarden-ipc/src/ipc_client.rs index a23865298..a765ef31b 100644 --- a/crates/bitwarden-ipc/src/ipc_client.rs +++ b/crates/bitwarden-ipc/src/ipc_client.rs @@ -191,7 +191,7 @@ mod tests { destination: Endpoint::BrowserBackground, topic: None, }; - let crypto_provider = NoiseCryptoProvider; + let crypto_provider = NoEncryptionCryptoProvider; let communication_provider = TestCommunicationBackend::new(); let session_map = InMemorySessionRepository::new(HashMap::new()); let client = IpcClient::new(crypto_provider, communication_provider.clone(), session_map);