bitwarden_ipc/crypto_provider/noise/
crypto_provider.rs1use std::time::Duration;
2
3use bitwarden_threading::time::timeout;
4use serde::{Deserialize, Serialize};
5use tracing::{debug, error, info, warn};
6
7use crate::{
8 crypto_provider::noise::{
9 NOISE_MAX_MESSAGE_LEN, NOISE_TAG_LEN,
10 handshake::{
11 CipherSuite, HandshakeFinishMessage, HandshakeInitiator, HandshakeResponder,
12 HandshakeStartMessage,
13 },
14 transport_state::{PersistentTransportState, TransportFrame},
15 },
16 error::{ErrorKind, IpcErrorKind},
17 message::{IncomingMessage, OutgoingMessage},
18 traits::{
19 CommunicationBackend, CommunicationBackendReceiver, CryptoProvider, SessionRepository,
20 },
21};
22
23#[derive(Default)]
25pub struct NoiseCryptoProvider {
26 crypto_state_guard: tokio::sync::Mutex<()>,
34}
35
36impl NoiseCryptoProvider {
37 pub fn new() -> Self {
39 Self::default()
40 }
41}
42
43#[derive(Debug)]
44pub enum NoiseCryptoProviderError {
45 HandshakeProtocol,
47 Timeout,
49 TransportUnreachable,
51 TransportSend { kind: ErrorKind },
54 TransportReceive { kind: ErrorKind },
57 DecryptionFailure,
59}
60
61impl IpcErrorKind for NoiseCryptoProviderError {
62 fn kind(&self) -> ErrorKind {
63 match self {
64 NoiseCryptoProviderError::HandshakeProtocol => ErrorKind::Other,
67 NoiseCryptoProviderError::Timeout => ErrorKind::Other,
69 NoiseCryptoProviderError::DecryptionFailure => ErrorKind::Other,
71 NoiseCryptoProviderError::TransportUnreachable => ErrorKind::Unreachable,
73 NoiseCryptoProviderError::TransportSend { kind }
75 | NoiseCryptoProviderError::TransportReceive { kind } => *kind,
76 }
77 }
78}
79
80fn transport_send_error<E: IpcErrorKind>(e: E) -> NoiseCryptoProviderError {
84 match e.kind() {
85 ErrorKind::Unreachable => NoiseCryptoProviderError::TransportUnreachable,
86 kind => NoiseCryptoProviderError::TransportSend { kind },
87 }
88}
89
90impl NoiseCryptoProvider {
91 async fn perform_handshake<Com, Ses>(
92 communication: &Com,
93 sessions: &Ses,
94 destination: crate::endpoint::Endpoint,
95 ) -> Result<(), NoiseCryptoProviderError>
96 where
97 Com: CommunicationBackend,
98 Ses: SessionRepository<NoiseCryptoProviderState>,
99 {
100 debug!("Starting noise handshake with {:?}", destination);
101
102 let mut initiator = HandshakeInitiator::new(&CipherSuite::default());
103 let message = initiator
104 .write_start_message()
105 .expect("Handshake start message should be buildable");
106 let receiver = communication.subscribe().await;
107
108 let handshake_frame = Frame::HandshakeStart(message);
109 communication
110 .send(OutgoingMessage {
111 payload: handshake_frame.to_cbor(),
112 destination: destination.clone(),
113 topic: None,
114 })
115 .await
116 .map_err(transport_send_error)?;
117
118 timeout(Duration::from_secs(HANDSHAKE_TIMEOUT_SECS), async {
120 loop {
121 let incoming = receiver
122 .receive()
123 .await
124 .map_err(|e| NoiseCryptoProviderError::TransportReceive { kind: e.kind() })?;
125
126 if incoming.source.to_endpoint() != destination {
128 continue;
129 }
130
131 let Ok(response_frame) = Frame::from_cbor(&incoming.payload) else {
133 return Err(NoiseCryptoProviderError::HandshakeProtocol);
134 };
135
136 if let Frame::HandshakeFinish(handshake_finish) = response_frame {
138 if initiator.read_response_message(&handshake_finish).is_err() {
139 error!("Failed to read handshake response message");
140 return Err(NoiseCryptoProviderError::HandshakeProtocol);
141 }
142 break;
143 }
144 }
145 Ok(())
146 })
147 .await
148 .map_err(|_| {
149 debug!(
150 "Noise handshake with {:?} timed out after {} seconds",
151 destination, HANDSHAKE_TIMEOUT_SECS
152 );
153 NoiseCryptoProviderError::Timeout
154 })??;
157
158 let crypto_state = NoiseCryptoProviderState {
159 state: (&mut initiator).into(),
160 };
161 sessions
162 .save(destination.clone(), crypto_state)
163 .await
164 .expect("Save session should not fail");
165
166 info!(
167 "Noise handshake with {:?} completed, session established",
168 destination
169 );
170
171 Ok(())
172 }
173}
174
175const REHANDSHAKE_INTERVAL_SECS: u64 = 300;
178
179const HANDSHAKE_TIMEOUT_SECS: u64 = 2;
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct NoiseCryptoProviderState {
185 state: PersistentTransportState,
186}
187
188impl<Com, Ses> CryptoProvider<Com, Ses> for NoiseCryptoProvider
189where
190 Com: CommunicationBackend,
191 Ses: SessionRepository<NoiseCryptoProviderState>,
192{
193 type Session = NoiseCryptoProviderState;
194 type SendError = NoiseCryptoProviderError;
195 type ReceiveError = NoiseCryptoProviderError;
196
197 fn max_payload_len(&self) -> usize {
198 NOISE_MAX_MESSAGE_LEN - NOISE_TAG_LEN
199 }
200
201 async fn send(
202 &self,
203 communication: &Com,
204 sessions: &Ses,
205 message: OutgoingMessage,
206 ) -> Result<(), Self::SendError> {
207 let _crypto_state_guard = self.crypto_state_guard.lock().await;
211
212 let destination = message.destination.clone();
213
214 let crypto_state = sessions
215 .get(destination.clone())
216 .await
217 .expect("Get session should not fail");
218
219 let mut should_handshake = crypto_state.is_none();
220 if let Some(state) = crypto_state.as_ref()
221 && state.state.should_rehandshake(REHANDSHAKE_INTERVAL_SECS)
222 {
223 info!(
224 "Noise session with {:?} is older than {}s, re-handshaking",
225 destination, REHANDSHAKE_INTERVAL_SECS
226 );
227 sessions
228 .remove(destination.clone())
229 .await
230 .expect("Delete session should not fail");
231 should_handshake = true;
232 }
233
234 if should_handshake {
235 if crypto_state.is_none() {
236 debug!(
237 "Noise handshake with {:?} initiated for new session establishment",
238 destination
239 );
240 } else {
241 debug!(
242 "Noise re-handshake with {:?} due to re-handshake interval",
243 destination
244 );
245 }
246
247 Self::perform_handshake(communication, sessions, destination.clone()).await?;
252 }
253
254 let mut crypto_state = sessions
255 .get(destination.clone())
256 .await
257 .expect("Get session should not fail")
258 .expect("Session should exist after handshake");
259
260 let transport_frame = crypto_state
262 .state
263 .send(message.payload.into())
264 .map_err(|_| NoiseCryptoProviderError::DecryptionFailure)?;
265 if let Err(e) = communication
266 .send(OutgoingMessage {
267 payload: Frame::TransportFrame(transport_frame).to_cbor(),
268 destination: destination.clone(),
269 topic: message.topic,
270 })
271 .await
272 .map_err(transport_send_error)
273 {
274 match e.kind() {
275 ErrorKind::Fatal => {
276 error!(
277 "{:?} fatal error sending message. Clearing cryptographic sessions.",
278 destination
279 );
280 sessions
281 .remove(destination.clone())
282 .await
283 .expect("Delete session should not fail");
284 return Err(e);
285 }
286 ErrorKind::Unreachable => {
287 info!(
294 "{:?} is unreachable. Clearing cryptographic sessions.",
295 destination
296 );
297 sessions
298 .remove(destination.clone())
299 .await
300 .expect("Delete session should not fail");
301 return Err(e);
302 }
303 ErrorKind::Other => {
305 error!(
306 "Recoverable error sending message to {:?}: {:?}",
307 destination, e
308 );
309 }
310 }
311 }
312
313 sessions
314 .save(destination, crypto_state)
315 .await
316 .expect("Save session should not fail");
317
318 Ok(())
319 }
320
321 async fn receive(
322 &self,
323 receiver: &Com::Receiver,
324 communication: &Com,
325 sessions: &Ses,
326 ) -> Result<IncomingMessage, Self::ReceiveError> {
327 loop {
328 let message = receiver
329 .receive()
330 .await
331 .map_err(|e| NoiseCryptoProviderError::TransportReceive { kind: e.kind() })?;
332
333 let source_endpoint: crate::endpoint::Endpoint = message.source.clone().into();
335
336 let Ok(transport_frame) = Frame::from_cbor(&message.payload) else {
338 warn!("Received malformed cbor message, ignoring");
339 continue;
340 };
341
342 match transport_frame {
343 Frame::HandshakeStart(handshake_start) => {
344 let mut responder = HandshakeResponder::new(&handshake_start.ciphersuite);
345 responder
346 .read_start_message(&handshake_start)
347 .map_err(|_| NoiseCryptoProviderError::HandshakeProtocol)?;
348 let response_message = responder
349 .write_response_message()
350 .map_err(|_| NoiseCryptoProviderError::HandshakeProtocol)?;
351 let handshake_frame = Frame::HandshakeFinish(response_message);
352 communication
353 .send(OutgoingMessage {
354 payload: handshake_frame.to_cbor(),
355 destination: source_endpoint.clone(),
356 topic: None,
357 })
358 .await
359 .map_err(transport_send_error)?;
360
361 let crypto_state = NoiseCryptoProviderState {
362 state: (&mut responder).into(),
363 };
364 sessions
365 .save(source_endpoint, crypto_state)
366 .await
367 .expect("Save session should not fail");
368 }
369 Frame::TransportFrame(transport_frame) => {
370 let _crypto_state_guard = self.crypto_state_guard.lock().await;
371 let crypto_state = sessions
372 .get(source_endpoint.clone())
373 .await
374 .expect("Get session should not fail");
375 let Some(mut state) = crypto_state else {
376 debug!("No session for {:?}, waiting for handshake", message.source);
377 let frame = Frame::CryptoInvalidated.to_cbor();
378 communication
379 .send(OutgoingMessage {
380 payload: frame,
381 destination: source_endpoint,
382 topic: None,
383 })
384 .await
385 .map_err(transport_send_error)?;
386 continue;
387 };
388
389 let payload = state.state.receive(&transport_frame);
390 let Ok(payload) = payload else {
391 info!("Failed to decrypt message from {:?}", message.source);
392 continue;
393 };
394
395 sessions
396 .save(source_endpoint, state)
397 .await
398 .expect("Save session should not fail");
399
400 return Ok(IncomingMessage {
401 payload: payload.as_ref().to_vec(),
402 destination: message.destination,
403 source: message.source,
404 topic: message.topic,
405 });
406 }
407 Frame::CryptoInvalidated => {
408 info!(
409 "Invalidated session for {:?} due to crypto error, deleting session and waiting for handshake",
410 message.source
411 );
412 sessions
413 .remove(source_endpoint)
414 .await
415 .expect("Delete session should not fail");
416 }
417 _ => continue,
418 }
419 }
420 }
421}
422
423#[derive(Serialize, Deserialize)]
425pub(super) enum Frame {
426 HandshakeStart(HandshakeStartMessage),
428 HandshakeFinish(HandshakeFinishMessage),
429 TransportFrame(TransportFrame),
431 CryptoInvalidated,
434}
435
436impl Frame {
437 pub(crate) fn to_cbor(&self) -> Vec<u8> {
438 let mut buffer = Vec::new();
439 ciborium::into_writer(self, &mut buffer).expect("Ciborium serialization should not fail");
440 buffer
441 }
442
443 pub(crate) fn from_cbor(buffer: &[u8]) -> Result<Self, ()> {
444 ciborium::from_reader(buffer).map_err(|_| ())
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use std::collections::HashMap;
451
452 use crate::{
453 IpcClientImpl,
454 crypto_provider::noise::crypto_provider::NoiseCryptoProvider,
455 endpoint::Endpoint,
456 ipc_client_trait::IpcClient,
457 message::OutgoingMessage,
458 traits::{InMemorySessionRepository, TestTwoWayCommunicationBackend},
459 };
460
461 #[tokio::test]
462 async fn ping_pong() {
463 let (provider_1, provider_2) = TestTwoWayCommunicationBackend::new();
464
465 let session_map_1 = InMemorySessionRepository::new(HashMap::new());
466 let client_1 = IpcClientImpl::new(NoiseCryptoProvider::new(), provider_1, session_map_1);
467 let _ = client_1.start(None).await;
468 let mut recv_1 = client_1.subscribe(None).await.unwrap();
469
470 let session_map_2 = InMemorySessionRepository::new(HashMap::new());
471 let client_2 = IpcClientImpl::new(NoiseCryptoProvider::new(), provider_2, session_map_2);
472 let _ = client_2.start(None).await;
473 let mut recv_2 = client_2.subscribe(None).await.unwrap();
474
475 let handle_1 = tokio::spawn(async move {
476 let mut val: u8 = 0;
477 for _ in 0..255 {
478 let message = OutgoingMessage {
479 payload: vec![val],
480 destination: Endpoint::DesktopMain,
481 topic: None,
482 };
483 client_1.send(message).await.unwrap();
484 let recv_message = recv_1.receive(None).await.unwrap();
485 val = recv_message.payload[0] + 1;
486 }
487 });
488
489 let handle_2 = tokio::spawn(async move {
490 for _ in 0..255 {
491 let recv_message = recv_2.receive(None).await.unwrap();
492 let val = recv_message.payload[0];
493 if val == 255 {
494 break;
495 }
496
497 client_2
498 .send(OutgoingMessage {
499 payload: vec![val],
500 destination: Endpoint::DesktopMain,
501 topic: None,
502 })
503 .await
504 .unwrap();
505 }
506 });
507
508 let _ = tokio::join!(handle_1, handle_2);
509 }
510}