diff --git a/credentialsd-ui/src/client.rs b/credentialsd-ui/src/client.rs index 56726838..b799a2f2 100644 --- a/credentialsd-ui/src/client.rs +++ b/credentialsd-ui/src/client.rs @@ -1,131 +1,8 @@ use async_std::{ channel::{Receiver, Sender}, - stream::Stream, sync::Mutex as AsyncMutex, }; -use credentialsd_common::{ - client::FlowController, - model::{RequestId, UserInteractedEvent}, - server::BackgroundEvent, -}; -use futures_lite::StreamExt; -use zbus::Connection; - -use crate::dbus::FlowControlServiceProxy; - -pub struct DbusCredentialClient { - conn: Connection, -} - -impl DbusCredentialClient { - pub fn new(conn: Connection) -> Self { - Self { conn } - } - async fn proxy(&self) -> std::result::Result, ()> { - FlowControlServiceProxy::new(&self.conn) - .await - .map_err(|err| tracing::error!("Failed to communicate with D-Bus service: {err}")) - } -} - -impl FlowController for DbusCredentialClient { - async fn get_available_public_key_devices( - &self, - ) -> std::result::Result, ()> { - self.proxy() - .await? - .get_available_public_key_devices() - .await - .map_err(|err| { - tracing::error!("Failed to retrieve available devices/transports: {err}") - }) - } - - async fn get_hybrid_credential(&mut self) -> std::result::Result<(), ()> { - self.proxy() - .await? - .get_hybrid_credential() - .await - .inspect_err(|err| tracing::error!("Failed to start hybrid credential flow: {err}")) - .map_err(|_| ()) - } - - async fn get_usb_credential(&mut self) -> std::result::Result<(), ()> { - self.proxy() - .await? - .get_usb_credential() - .await - .inspect_err(|err| tracing::error!("Failed to start USB credential flow: {err}")) - .map_err(|_| ()) - } - - async fn get_nfc_credential(&mut self) -> std::result::Result<(), ()> { - self.proxy() - .await? - .get_nfc_credential() - .await - .inspect_err(|err| tracing::error!("Failed to start NFC credential flow: {err}")) - .map_err(|_| ()) - } - - async fn subscribe( - &mut self, - ) -> std::result::Result< - std::pin::Pin< - Box + Send + 'static>, - >, - (), - > { - let stream = self - .proxy() - .await? - .receive_state_changed() - .await - .map_err(|err| tracing::error!("Failed to initalize event stream: {err}"))? - .filter_map(|msg| { - msg.args() - .map(|args| args.update) - .inspect_err(|err| tracing::warn!("Failed to parse StateChanged signal: {err}")) - .ok() - }) - .boxed(); - self.proxy() - .await? - .subscribe() - .await - .map_err(|err| tracing::error!("Failed to initialize event stream: {err}")) - .map(|_| stream) - } - - async fn enter_client_pin(&mut self, pin: String) -> std::result::Result<(), ()> { - self.proxy() - .await? - .enter_client_pin(pin) - .await - .map_err(|err| tracing::error!("Failed to send PIN to authenticator: {err}")) - } - - async fn select_credential(&self, credential_id: String) -> std::result::Result<(), ()> { - self.proxy() - .await? - .select_credential(credential_id) - .await - .map_err(|err| tracing::error!("Failed to select credential: {err}")) - } - - async fn cancel_request(&self, request_id: RequestId) -> Result<(), ()> { - if self - .proxy() - .await? - .cancel_request(request_id) - .await - .is_err() - { - tracing::warn!("Failed to cancel request {request_id}"); - } - Ok(()) - } -} +use credentialsd_common::{model::UserInteractedEvent, server::BackgroundEvent}; #[derive(Debug)] pub struct FlowControlClient { diff --git a/credentialsd-ui/src/dbus.rs b/credentialsd-ui/src/dbus.rs index 5c2045ab..d050a2a8 100644 --- a/credentialsd-ui/src/dbus.rs +++ b/credentialsd-ui/src/dbus.rs @@ -1,22 +1,19 @@ use std::sync::Arc; use async_std::{ - channel::{self, Receiver, Sender}, - stream::StreamExt, + channel::{self, Sender}, sync::Mutex as AsyncMutex, task::JoinHandle, }; use zbus::{ - Connection, ObjectServer, fdo, interface, + ObjectServer, fdo, interface, message::Header, names::{BusName, OwnedUniqueName}, object_server::SignalEmitter, - proxy, zvariant::{ObjectPath, Optional}, }; use credentialsd_common::{ - client::FlowController, model::{ Device, Operation, PortalBackendOptions, RequestId, RequestingApplication, UserInteractedEvent, @@ -24,103 +21,7 @@ use credentialsd_common::{ server::{BackgroundEvent, ViewRequest, WindowHandle}, }; -use crate::client::{DbusCredentialClient, FlowControlClient}; - -#[proxy( - gen_blocking = false, - interface = "xyz.iinuwa.credentialsd.FlowControl1", - default_path = "/xyz/iinuwa/credentialsd/FlowControl", - default_service = "xyz.iinuwa.credentialsd.FlowControl" -)] -pub trait FlowControlService { - async fn subscribe(&self) -> fdo::Result<()>; - - async fn get_available_public_key_devices(&self) -> fdo::Result>; - - async fn get_hybrid_credential(&self) -> fdo::Result<()>; - - async fn get_usb_credential(&self) -> fdo::Result<()>; - async fn get_nfc_credential(&self) -> fdo::Result<()>; - - async fn select_device(&self, device_id: String) -> fdo::Result<()>; - async fn enter_client_pin(&self, pin: String) -> fdo::Result<()>; - async fn select_credential(&self, credential_id: String) -> fdo::Result<()>; - async fn cancel_request(&self, request_id: RequestId) -> fdo::Result<()>; - - #[zbus(signal)] - async fn state_changed(update: BackgroundEvent) -> zbus::Result<()>; -} - -pub struct UiControlService { - pub request_tx: Sender<(ViewRequest, Arc>)>, -} - -/// These methods are called by the credential service to control the UI. -#[interface(name = "xyz.iinuwa.credentialsd.UiControl1")] -impl UiControlService { - async fn launch_ui( - &self, - #[zbus(connection)] conn: &Connection, - request: ViewRequest, - ) -> fdo::Result<()> { - tracing::debug!("Received UI launch request"); - let mut client = DbusCredentialClient::new(conn.clone()); - let (fc_tx, fc_rx) = async_std::channel::unbounded(); - let (bg_tx, bg_rx) = async_std::channel::unbounded(); - match client.subscribe().await { - Ok(mut bg_event_stream) => async_std::task::spawn(async move { - while let Some(bg_event) = bg_event_stream.next().await { - if let Err(_) = bg_tx.send(bg_event).await { - tracing::debug!("Background event receiver dropped. Stopping."); - break; - } - } - }), - Err(_) => { - tracing::error!( - ?request, - "Failed to subscribe to background events for request" - ); - return Err(fdo::Error::Failed( - "Failed to subscribe to background events for request".to_string(), - )); - } - }; - async_std::task::spawn(async move { - while let Ok(msg) = fc_rx.recv().await { - // UI doesn't get an error if these fail... - let result = match &msg { - UserInteractedEvent::HybridDiscoveryRequested => { - client.get_hybrid_credential().await - } - UserInteractedEvent::NfcDiscoveryRequested => client.get_nfc_credential().await, - UserInteractedEvent::UsbDiscoveryRequested => client.get_usb_credential().await, - UserInteractedEvent::ClientPinEntered(pin) => { - client.enter_client_pin(pin.to_string()).await - } - UserInteractedEvent::CredentialSelected(cred_id) => { - client.select_credential(cred_id.to_string()).await - } - UserInteractedEvent::RequestCancelled => { - client.cancel_request(request.id).await - } - }; - if let Err(err) = result { - tracing::error!("Failed to send {msg:?} to frontend: {err:?}"); - } - } - client - }); - let flow_control_client = FlowControlClient { - tx: fc_tx, - rx: AsyncMutex::new(Some(bg_rx)), - }; - self.request_tx - .send((request, Arc::new(AsyncMutex::new(flow_control_client)))) - .await - .map_err(|_| fdo::Error::Failed("UI failed to launch".to_string())) - } -} +use crate::client::FlowControlClient; pub struct CredentialPortalBackend { pub request_tx: Sender<(ViewRequest, Arc>)>, diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index 49a213e6..9fb4d82c 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -13,9 +13,7 @@ use gettextrs::gettext; use serde::{Deserialize, Serialize}; use tracing::{error, info}; -use credentialsd_common::model::{ - Device, Error, HybridState, NfcState, Operation, Transport, UsbState, ViewUpdate, -}; +use credentialsd_common::model::{Device, HybridState, Operation, Transport, ViewUpdate}; use crate::client::FlowControlClient; diff --git a/credentialsd-ui/src/main.rs b/credentialsd-ui/src/main.rs index 6aa2aa62..a75ec19f 100644 --- a/credentialsd-ui/src/main.rs +++ b/credentialsd-ui/src/main.rs @@ -6,7 +6,7 @@ mod gui; use std::error::Error; -use crate::dbus::{CredentialPortalBackend, UiControlService}; +use crate::dbus::CredentialPortalBackend; fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); @@ -23,15 +23,10 @@ async fn run() -> Result<(), Box> { println!(" ✅"); print!("Starting UI Control listener...\t"); - let interface = UiControlService { - request_tx: request_tx.clone(), - }; let portal_backend_interface = CredentialPortalBackend { request_tx }; - let path = "/xyz/iinuwa/credentialsd/UiControl"; let service = "xyz.iinuwa.credentialsd.UiControl"; let _server_conn = zbus::connection::Builder::session()? .name(service)? - .serve_at(path, interface)? .serve_at("/org/freedesktop/portal/desktop", portal_backend_interface)? .build() .await?; diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index f71e7c90..5ab0cc3c 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -2,82 +2,47 @@ //! the credential request through the trusted UI. use std::sync::Mutex; -use std::{collections::VecDeque, fmt::Debug, sync::Arc}; +use std::{fmt::Debug, sync::Arc}; use async_trait::async_trait; use credentialsd_common::model::{ - Device, Error as CredentialServiceError, Operation, PortalBackendOptions, RequestId, - RequestingApplication, UserInteractedEvent, WebAuthnError, + Error as CredentialServiceError, Operation, PortalBackendOptions, RequestingApplication, + UserInteractedEvent, WebAuthnError, }; -use credentialsd_common::server::{BackgroundEvent, ViewRequest, WindowHandle}; +use credentialsd_common::server::{BackgroundEvent, WindowHandle}; use futures_lite::{Stream, StreamExt}; +use tokio::sync::mpsc::Receiver; use tokio::sync::oneshot; -use tokio::{ - sync::{ - mpsc::{self, Sender}, - Mutex as AsyncMutex, - }, - task::AbortHandle, -}; -use zbus::{ - connection::{Builder, Connection}, - fdo, interface, - object_server::{InterfaceRef, SignalEmitter}, - ObjectServer, -}; +use tokio::sync::{mpsc::Sender, Mutex as AsyncMutex}; +use tokio::task::AbortHandle; +use zbus::connection::Connection; use crate::credential_service::ManageDevice; use crate::dbus::ui_control::Ceremony; use crate::dbus::UiControlServiceClient; use crate::{ - credential_service::{hybrid::HybridState, nfc::NfcState, UsbState}, + credential_service::UsbState, dbus::ui_control::UiController, model::{CredentialRequest, CredentialResponse}, }; -pub const SERVICE_PATH: &str = "/xyz/iinuwa/credentialsd/FlowControl"; -pub const SERVICE_NAME: &str = "xyz.iinuwa.credentialsd.FlowControl"; - pub async fn start_flow_control_service( - device_manager: M, -) -> zbus::Result<( - Connection, - Sender<( + conn: Connection, + mut listener: Receiver<( CredentialRequest, RequestingApplication, Option, // Client window handle oneshot::Sender>, )>, -)> { + device_manager: M, +) -> zbus::Result { let svc = Arc::new(AsyncMutex::new(device_manager)); let svc2 = svc.clone(); - let conn = Builder::session()? - .name(SERVICE_NAME)? - .serve_at( - SERVICE_PATH, - FlowControlDbusService { - signal_state: Arc::new(AsyncMutex::new(SignalState::Idle)), - svc, - pin_tx: Arc::new(AsyncMutex::new(None)), - cred_tx: Arc::new(AsyncMutex::new(None)), - usb_event_forwarder_task: Arc::new(AsyncMutex::new(None)), - nfc_event_forwarder_task: Arc::new(AsyncMutex::new(None)), - hybrid_event_forwarder_task: Arc::new(AsyncMutex::new(None)), - }, - )? - .build() - .await?; - let (initiator_tx, mut initiator_rx) = mpsc::channel::<( - CredentialRequest, - RequestingApplication, - Option, - oneshot::Sender>, - )>(2); - let conn2 = conn.clone(); - tokio::spawn(async move { - while let Some((msg, requesting_app, window_handle, tx)) = initiator_rx.recv().await { + + let task = tokio::spawn(async move { + while let Some((msg, requesting_app, window_handle, tx)) = listener.recv().await { let svc = svc2.clone(); - let ui_control_client = UiControlServiceClient::new(conn2.clone()); + let ui_control_client = UiControlServiceClient::new(conn.clone()); if let Err(_) = tx.send(handle(svc, ui_control_client, msg, requesting_app, window_handle).await) { @@ -87,7 +52,7 @@ pub async fn start_flow_control_service( @@ -261,273 +226,6 @@ fn forward_background_event_stream( }); } -struct FlowControlService { - svc: Arc>, - signal_state: Arc>, - pin_tx: Arc>>>, - cred_tx: Arc>>>, - usb_event_forwarder_task: Arc>>, - nfc_event_forwarder_task: Arc>>, - hybrid_event_forwarder_task: Arc>>, -} - -impl FlowControlService { - fn send_update(&self) {} -} - -struct FlowControlDbusService { - svc: Arc>, - - signal_state: Arc>, - - cred_tx: Arc>>>, - pin_tx: Arc>>>, - - hybrid_event_forwarder_task: Arc>>, - nfc_event_forwarder_task: Arc>>, - usb_event_forwarder_task: Arc>>, -} - -/// The following methods are for communication between the [trusted] -/// UI and the credential service, and should not be called by arbitrary -/// clients. -#[interface( - name = "xyz.iinuwa.credentialsd.FlowControl1", - proxy( - gen_blocking = false, - default_path = "/xyz/iinuwa/credentialsd/FlowControl", - default_service = "xyz.iinuwa.credentialsd.FlowControl", - ) -)] -impl FlowControlDbusService -where - M: ManageDevice + Debug + Send + Sync + 'static, -{ - async fn subscribe( - &self, - #[zbus(signal_emitter)] emitter: SignalEmitter<'_>, - ) -> fdo::Result<()> { - let mut signal_state = self.signal_state.lock().await; - match *signal_state { - SignalState::Idle => {} - SignalState::Pending(ref mut pending) => { - for msg in pending.iter_mut() { - emitter.state_changed(msg.clone()).await?; - } - } - SignalState::Active => {} - }; - *signal_state = SignalState::Active; - Ok(()) - } - - async fn get_available_public_key_devices(&self) -> fdo::Result> { - let devices = self - .svc - .lock() - .await - .get_available_public_key_devices() - .await - .map_err(|_| fdo::Error::Failed("Failed to retrieve available devices".to_string()))?; - let dbus_devices: Vec = devices.into_iter().map(Device::from).collect(); - - Ok(dbus_devices) - } - - async fn get_hybrid_credential( - &self, - #[zbus(object_server)] object_server: &ObjectServer, - ) -> fdo::Result<()> { - let svc = self.svc.lock().await; - let mut stream = svc.get_hybrid_credential().await; - let signal_state = self.signal_state.clone(); - let object_server = object_server.clone(); - let task = tokio::spawn(async move { - let interface: zbus::Result>> = - object_server.interface(SERVICE_PATH).await; - - let emitter = match interface { - Ok(ref i) => i.signal_emitter(), - Err(err) => { - tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); - return; - } - }; - while let Some(state) = stream.next().await { - let event = (&state).into(); - if let Err(err) = send_state_update(emitter, &signal_state, event).await { - tracing::error!("Failed to send state update to UI: {err}"); - break; - }; - match state { - HybridState::Completed | HybridState::Failed => { - break; - } - _ => {} - }; - } - }) - .abort_handle(); - if let Some(prev_task) = self.hybrid_event_forwarder_task.lock().await.replace(task) { - prev_task.abort(); - } - Ok(()) - } - - async fn get_usb_credential( - &self, - #[zbus(object_server)] object_server: &ObjectServer, - ) -> fdo::Result<()> { - let mut stream = self.svc.lock().await.get_usb_credential().await; - let usb_pin_tx = self.pin_tx.clone(); - let usb_cred_tx = self.cred_tx.clone(); - let signal_state = self.signal_state.clone(); - let object_server = object_server.clone(); - let task = tokio::spawn(async move { - let interface: zbus::Result>> = - object_server.interface(SERVICE_PATH).await; - - let emitter = match interface { - Ok(ref i) => i.signal_emitter(), - Err(err) => { - tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); - return; - } - }; - while let Some(state) = stream.next().await { - let event = (&state).into(); - if let Err(err) = send_state_update(emitter, &signal_state, event).await { - tracing::error!("Failed to send state update to UI: {err}"); - break; - }; - match state { - UsbState::NeedsPin { pin_tx, .. } => { - let mut usb_pin_tx = usb_pin_tx.lock().await; - let _ = usb_pin_tx.insert(pin_tx); - } - UsbState::SelectingCredential { cred_tx, .. } => { - let mut usb_cred_tx = usb_cred_tx.lock().await; - let _ = usb_cred_tx.insert(cred_tx); - } - UsbState::Completed | UsbState::Failed(_) => { - break; - } - _ => {} - }; - } - }) - .abort_handle(); - if let Some(prev_task) = self.usb_event_forwarder_task.lock().await.replace(task) { - prev_task.abort(); - } - Ok(()) - } - - async fn get_nfc_credential( - &self, - #[zbus(object_server)] object_server: &ObjectServer, - ) -> fdo::Result<()> { - let mut stream = self.svc.lock().await.get_nfc_credential().await; - let nfc_pin_tx = self.pin_tx.clone(); - let nfc_cred_tx = self.cred_tx.clone(); - let signal_state = self.signal_state.clone(); - let object_server = object_server.clone(); - let task = tokio::spawn(async move { - let interface: zbus::Result>> = - object_server.interface(SERVICE_PATH).await; - - let emitter = match interface { - Ok(ref i) => i.signal_emitter(), - Err(err) => { - tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); - return; - } - }; - while let Some(state) = stream.next().await { - let event = (&state).into(); - if let Err(err) = send_state_update(emitter, &signal_state, event).await { - tracing::error!("Failed to send state update to UI: {err}"); - break; - }; - match state { - NfcState::NeedsPin { pin_tx, .. } => { - let mut nfc_pin_tx = nfc_pin_tx.lock().await; - let _ = nfc_pin_tx.insert(pin_tx); - } - NfcState::SelectingCredential { cred_tx, .. } => { - let mut nfc_cred_tx = nfc_cred_tx.lock().await; - let _ = nfc_cred_tx.insert(cred_tx); - } - NfcState::Completed | NfcState::Failed(_) => { - break; - } - _ => {} - }; - } - }) - .abort_handle(); - if let Some(prev_task) = self.nfc_event_forwarder_task.lock().await.replace(task) { - prev_task.abort(); - } - Ok(()) - } - - async fn enter_client_pin(&self, pin: String) -> fdo::Result<()> { - if let Some(pin_tx) = self.pin_tx.lock().await.take() { - pin_tx.send(pin).await.unwrap(); - } - Ok(()) - } - - async fn select_credential(&self, credential_id: String) -> fdo::Result<()> { - if let Some(cred_tx) = self.cred_tx.lock().await.take() { - cred_tx.send(credential_id).await.unwrap(); - } - Ok(()) - } - - async fn cancel_request(&self, request_id: RequestId) -> fdo::Result<()> { - self.svc.lock().await.cancel_request(request_id).await; - Ok(()) - } - - #[zbus(signal)] - async fn state_changed( - emitter: &SignalEmitter<'_>, - update: BackgroundEvent, - ) -> zbus::Result<()>; -} -async fn send_state_update( - emitter: &SignalEmitter<'_>, - signal_state: &Arc>, - update: BackgroundEvent, -) -> fdo::Result<()> { - let mut signal_state = signal_state.lock().await; - match *signal_state { - SignalState::Idle => { - let pending = VecDeque::from([update]); - *signal_state = SignalState::Pending(pending); - } - SignalState::Pending(ref mut pending) => { - pending.push_back(update); - } - SignalState::Active => { - emitter.state_changed(update).await?; - } - }; - Ok(()) -} - -enum SignalState { - /// No state - Idle, - /// Waiting for client to signal that it's ready to receive events. - /// Holds a cache of events to send once the client connects. - Pending(VecDeque), - /// Client is actively receiving messages. - Active, -} - /// Coordinates between user and various devices connected to the machine to /// fulfill credential requests. #[async_trait] diff --git a/credentialsd/src/dbus/ui_control.rs b/credentialsd/src/dbus/ui_control.rs index 2d9cd7db..7accb867 100644 --- a/credentialsd/src/dbus/ui_control.rs +++ b/credentialsd/src/dbus/ui_control.rs @@ -9,22 +9,17 @@ use tokio::sync::{ }; use zbus::{ fdo, proxy, - zvariant::{ObjectPath, Optional, OwnedObjectPath}, + zvariant::{Optional, OwnedObjectPath}, Connection, }; use credentialsd_common::{ model::{Device, Operation, PortalBackendOptions, RequestId, UserInteractedEvent}, - server::{BackgroundEvent, ViewRequest, WindowHandle}, + server::{BackgroundEvent, WindowHandle}, }; /// Used by the credential service to control the UI. pub trait UiController { - fn launch_ui( - &self, - request: ViewRequest, - ) -> impl Future>> + Send; - fn initialize( &self, parent_window: Option, @@ -40,17 +35,6 @@ pub trait UiController { ) -> impl Future>> + Send; } -#[proxy( - gen_blocking = false, - interface = "xyz.iinuwa.credentialsd.UiControl1", - default_service = "xyz.iinuwa.credentialsd.UiControl", - default_path = "/xyz/iinuwa/credentialsd/UiControl" -)] -trait UiControlService { - fn launch_ui(&self, request: ViewRequest) -> fdo::Result<()>; - fn cancel_request(&self, request_id: RequestId) -> fdo::Result<()>; -} - #[proxy( gen_blocking = false, interface = "org.freedesktop.impl.portal.experimental.Credential", @@ -122,35 +106,12 @@ impl UiControlServiceClient { Self { conn } } - async fn proxy(&self) -> Result, zbus::Error> { - UiControlServiceProxy::new(&self.conn).await - } - async fn proxy2(&self) -> Result, zbus::Error> { UiControlService2Proxy::new(&self.conn).await } - - async fn request_proxy( - &self, - request_id: RequestId, - ) -> Result, zbus::Error> { - let object_path = ObjectPath::from_string_unchecked(format!( - "/org/freedesktop/portal/Credential/{}", - request_id - )); - CeremonyObjectProxy::new(&self.conn, object_path).await - } } impl UiController for UiControlServiceClient { - async fn launch_ui(&self, request: ViewRequest) -> Result<(), Box> { - self.proxy() - .await? - .launch_ui(request) - .await - .map_err(|err| err.into()) - } - async fn initialize( &self, parent_window: Option, @@ -245,19 +206,6 @@ pub mod test { } impl UiController for DummyUiClient { - async fn launch_ui(&self, request: ViewRequest) -> Result<(), Box> { - tracing::debug!( - target: "DummyUiClient", - "Sending launch_ui() request" - ); - self.tx.send(request).await.unwrap(); - tracing::debug!( - target: "DummyUiClient", - "Finish launch_ui() request" - ); - Ok(()) - } - async fn initialize( &self, _parent_window: Option, diff --git a/credentialsd/src/gateway/dbus.rs b/credentialsd/src/gateway/dbus.rs index 8652dea3..4f8ef8b0 100644 --- a/credentialsd/src/gateway/dbus.rs +++ b/credentialsd/src/gateway/dbus.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, fmt::Display, os::fd::AsRawFd, sync::Arc}; use serde::{ser::SerializeTuple, Deserialize, Serialize}; use tokio::sync::Mutex as AsyncMutex; use zbus::{ - fdo, interface, + interface, message::Header, names::{BusName, UniqueName}, zvariant::{DeserializeDict, Optional, Type, Value}, @@ -11,21 +11,17 @@ use zbus::{ }; use credentialsd_common::{ - model::{GetClientCapabilitiesResponse, RequestingApplication, WebAuthnError}, + model::WebAuthnError, server::{ CreateCredentialRequest, CreateCredentialResponse, CreatePublicKeyCredentialRequest, GetCredentialRequest, GetCredentialResponse, GetPublicKeyCredentialRequest, WindowHandle, }, }; -use crate::webauthn::{AppId, Origin}; +use crate::{webauthn::AppId, DBUS_SERVICE_NAME}; -use super::{ - check_origin_from_app, get_app_info_from_pid, GatewayService, RequestContext, RequestKind, -}; +use super::{check_origin_from_app, GatewayService, RequestContext}; -pub const SERVICE_NAME: &str = "xyz.iinuwa.credentialsd.Credentials"; -pub const SERVICE_PATH: &str = "/xyz/iinuwa/credentialsd/Credentials"; pub const PORTAL_SERVICE_PATH: &str = "/org/freedesktop/portal/desktop"; pub(super) async fn start_dbus_gateway( @@ -35,13 +31,7 @@ pub(super) async fn start_dbus_gateway( .inspect_err(|err| { tracing::error!("Failed to connect to D-Bus session: {err}"); })? - .name(SERVICE_NAME)? - .serve_at( - SERVICE_PATH, - CredentialGateway { - gateway_service: svc.clone(), - }, - )? + .name(DBUS_SERVICE_NAME)? .serve_at( PORTAL_SERVICE_PATH, CredentialPortalGateway { @@ -52,131 +42,6 @@ pub(super) async fn start_dbus_gateway( .await } -/// Struct to hold state for the privileged D-Bus interface. -struct CredentialGateway { - /// Service responsible for processing credential requests. - gateway_service: Arc>, -} - -/// These are public methods that can be called by arbitrary clients to begin a -/// credential flow. -/// -/// The D-Bus interface is responsible for authorizing the client and collecting -/// the contextual information about the client to pass onto the GatewayService -/// for evaluation. -#[interface(name = "xyz.iinuwa.credentialsd.Credentials1")] -impl CredentialGateway { - async fn create_credential( - &self, - #[zbus(header)] header: Header<'_>, - #[zbus(connection)] connection: &Connection, - parent_window: Optional, - request: CreateCredentialRequest, - ) -> Result { - let context = extract_client_details( - header, - connection, - request.origin.as_ref().cloned(), - request.is_same_origin.unwrap_or_default(), - ) - .await?; - - let response = self - .gateway_service - .lock() - .await - .handle_create_credential(request, context, parent_window.into()) - .await?; - Ok(response) - } - - async fn get_credential( - &self, - #[zbus(header)] header: Header<'_>, - #[zbus(connection)] connection: &Connection, - parent_window: Optional, - request: GetCredentialRequest, - ) -> Result { - let context = extract_client_details( - header, - connection, - request.origin.as_ref().cloned(), - request.is_same_origin.unwrap_or_default(), - ) - .await?; - - let response = self - .gateway_service - .lock() - .await - .handle_get_credential(request, context, parent_window.into()) - .await?; - Ok(response) - } - - async fn get_client_capabilities(&self) -> fdo::Result { - let capabilities = self - .gateway_service - .lock() - .await - .handle_get_client_capabilities(); - Ok(capabilities) - } -} - -/// Returns contextual details about the client and the request needed for -/// authorization. -async fn extract_client_details( - header: Header<'_>, - connection: &Connection, - origin: Option, - is_same_origin: bool, -) -> Result { - let top_origin = if is_same_origin { - None - } else { - // TODO: Once we modify the models to convey the top-origin in cross origin requests to the UI, we can remove this error message. - // We should still reject cross-origin requests for conditionally-mediated requests. - tracing::warn!("Client attempted to issue cross-origin request for credentials, which are not supported by this platform."); - return Err(WebAuthnError::NotAllowedError.into()); - }; - /* - let top_origin = - top_origin.as_ref() - .map(|o| o.parse::()) - .transpose() - .map_err(|err| { - tracing::warn!(%err, "Invalid top origin specified: {:?}", client_details.top_origin); - WebAuthnError::SecurityError - })?; - */ - - let Some(origin) = origin.as_ref().cloned() else { - tracing::warn!( - "Caller requested implicit origin, which is not yet implemented. Rejecting request." - ); - return Err(Error::SecurityError); - }; - let origin = origin.parse::().map_err(|err| { - tracing::warn!(%err, "Invalid origin specified: {:?}", origin); - WebAuthnError::SecurityError - })?; - - // Find out where this request is coming from (which application is requesting this) - let requesting_app = query_connection_peer_binary(header, connection) - .await - .ok_or_else(|| { - tracing::error!("Could not retrieve client details from D-Bus connection"); - Error::SecurityError - })?; - Ok(RequestContext { - app_id: "xyz.iinuwa.credentialsd.CredentialGateway".parse().unwrap(), // hardcoding this for now; this will be obsolete soon - app_name: requesting_app.name.as_ref().unwrap().clone(), - pid: requesting_app.pid, - request_kind: RequestKind::Privileged { origin, top_origin }, - }) -} - /// Struct to hold state for the portal D-Bus interface. struct CredentialPortalGateway { /// Service responsible for processing credential requests. @@ -581,70 +446,3 @@ async fn query_peer_pid_via_fdinfo( Some(pid) } - -async fn query_peer_pid_via_dbus( - connection: &Connection, - sender_unique_name: &UniqueName<'_>, -) -> Option { - // Use the connection to query the D-Bus daemon for more info - let proxy = match zbus::Proxy::new( - connection, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - ) - .await - { - Ok(p) => p, - Err(e) => { - tracing::error!("Failed to establish DBus proxy to query peer info: {e:?}"); - return None; - } - }; - - // Get the Process ID (PID) of the peer - let pid_result = match proxy - .call_method("GetConnectionUnixProcessID", &(sender_unique_name)) - .await - { - Ok(pid) => pid, - Err(e) => { - tracing::error!("Failed to get peer PID via DBus: {e:?}"); - return None; - } - }; - let pid: u32 = match pid_result.body().deserialize() { - Ok(pid) => pid, - Err(e) => { - tracing::error!("Retrieved peer PID is not an integer: {e:?}"); - return None; - } - }; - Some(pid) -} - -async fn query_connection_peer_binary( - header: Header<'_>, - connection: &Connection, -) -> Option { - // Get the sender's unique bus name - let sender_unique_name = header.sender()?; - - tracing::debug!("Received request from sender: {}", sender_unique_name); - - // Get the senders PID. - // - // First, try to get the PID via the more secure fdinfo - let mut pid = query_peer_pid_via_fdinfo(connection, sender_unique_name).await; - // If that fails, we fall back to asking dbus directly for the peers PID - if pid.is_none() { - pid = query_peer_pid_via_dbus(connection, sender_unique_name).await; - } - - let Some(pid) = pid else { - tracing::error!("Failed to determine peers PID. Skipping application details query."); - return None; - }; - - get_app_info_from_pid(pid) -} diff --git a/credentialsd/src/gateway/mod.rs b/credentialsd/src/gateway/mod.rs index 266b4ab3..f395986d 100644 --- a/credentialsd/src/gateway/mod.rs +++ b/credentialsd/src/gateway/mod.rs @@ -10,7 +10,7 @@ use std::{ }; use credentialsd_common::{ - model::{GetClientCapabilitiesResponse, RequestingApplication, WebAuthnError}, + model::{RequestingApplication, WebAuthnError}, server::{ CreateCredentialRequest, CreateCredentialResponse, GetCredentialRequest, GetCredentialResponse, WindowHandle, @@ -192,20 +192,6 @@ impl GatewayService { Err(WebAuthnError::TypeError) } } - - fn handle_get_client_capabilities(&self) -> GetClientCapabilitiesResponse { - GetClientCapabilitiesResponse { - conditional_create: false, - conditional_get: false, - hybrid_transport: true, - passkey_platform_authenticator: true, - user_verifying_platform_authenticator: false, - related_origins: false, - signal_all_accepted_credentials: false, - signal_current_user_details: false, - signal_unknown_credential: false, - } - } } /// Verifies that the calling client is able to request credentials for the @@ -231,41 +217,6 @@ fn validate_request(context: &RequestContext) -> Result Option { - // Get binary path via PID from /proc file-system - // TODO: To be REALLY sure, we may want to look at /proc/PID/exe instead. It is a symlink to - // the actual binary, giving a full path instead of only the command name. - // This should in theory be "more secure", but also may disconcert novice users with no - // technical background. - let command_name = match std::fs::read_to_string(format!("/proc/{pid}/comm")) { - Ok(c) => c.trim().to_string(), - Err(e) => { - tracing::error!( - "Failed to read /proc/{pid}/comm, so we don't know the command name of peer: {e:?}" - ); - return None; - } - }; - tracing::debug!("Request is from: {command_name}"); - - let exe_path = match std::fs::read_link(format!("/proc/{pid}/exe")) { - Ok(p) => p, - Err(e) => { - tracing::error!( - "Failed to follow link of /proc/{pid}/exe, so we don't know the executable path of peer: {e:?}" - ); - return None; - } - }; - tracing::debug!("Request is from: {exe_path:?}"); - - Some(RequestingApplication { - name: Some(command_name).into(), - path_or_app_id: exe_path.to_string_lossy().to_string(), - pid, - }) -} - async fn should_trust_app_id(pid: u32) -> bool { // Verify if we should trust the peer based on the file name. We verify that // we're in the same mount namespace before using the exe path. diff --git a/credentialsd/src/main.rs b/credentialsd/src/main.rs index 3705eea1..3231c786 100644 --- a/credentialsd/src/main.rs +++ b/credentialsd/src/main.rs @@ -4,17 +4,20 @@ mod gateway; mod model; mod webauthn; -use std::{error::Error, sync::Arc}; +use std::error::Error; use credential_service::nfc::InProcessNfcHandler; +use tokio::sync::mpsc; use crate::{ credential_service::{ hybrid::InternalHybridHandler, usb::InProcessUsbHandler, CredentialService, }, - dbus::{CredentialRequestControllerClient, UiControlServiceClient}, + dbus::CredentialRequestControllerClient, }; +pub const DBUS_SERVICE_NAME: &str = "xyz.iinuwa.credentialsd.Credentials"; + #[tokio::main] async fn main() { // Initialize logger @@ -25,30 +28,32 @@ async fn main() { } async fn run() -> Result<(), Box> { - print!("Connecting to D-Bus as client...\t"); - let dbus_client_conn = zbus::connection::Builder::session()?.build().await?; + print!("Starting D-Bus public client service..."); + let (incoming_request_tx, incoming_request_rx) = mpsc::channel(2); + let request_controller = CredentialRequestControllerClient { + initiator: incoming_request_tx, + }; + let dbus_conn = gateway::start_gateway(request_controller).await?; println!(" ✅"); - print!("Starting D-Bus UI -> Credential control service..."); - let ui_controller = UiControlServiceClient::new(dbus_client_conn); + // initialize client to interact with UI let credential_service = CredentialService::new( InternalHybridHandler::new(), InProcessNfcHandler {}, InProcessUsbHandler {}, ); - let (_flow_control_conn, initiator) = - dbus::start_flow_control_service(credential_service).await?; - println!(" ✅"); - - print!("Starting D-Bus public client service..."); - let initiator = CredentialRequestControllerClient { initiator }; - let _gateway_conn = gateway::start_gateway(initiator).await?; - println!(" ✅"); + let flow_control_svc = dbus::start_flow_control_service( + dbus_conn.clone(), + incoming_request_rx, + credential_service, + ) + .await?; println!("Waiting for messages..."); tokio::signal::ctrl_c() .await .map_err(|err| format!("Failed to wait for shutdown signals: {err}. Shutting down"))?; + flow_control_svc.abort(); Ok(()) }