From aa3f8286808eb598d38372ced56f0596b68b7758 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 12:11:02 -0500 Subject: [PATCH 1/7] common: Remove request_id from backend interface --- credentialsd-common/src/server.rs | 2 +- credentialsd-ui/src/dbus.rs | 6 +----- credentialsd-ui/src/main.rs | 5 +---- credentialsd/src/dbus/flow_control.rs | 1 - credentialsd/src/dbus/ui_control.rs | 6 +----- 5 files changed, 4 insertions(+), 16 deletions(-) diff --git a/credentialsd-common/src/server.rs b/credentialsd-common/src/server.rs index c670e75..fbe7d2e 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -11,7 +11,7 @@ use zvariant::{ SerializeDict, Signature, Str, Structure, StructureBuilder, Type, Value, signature::Fields, }; -use crate::model::{Device, Operation, RequestId, UserInteractedEvent}; +use crate::model::{Device, Operation, UserInteractedEvent}; const TAG_VALUE_SIGNATURE: &Signature = &Signature::Structure(Fields::Static { fields: &[&Signature::U32, &Signature::Variant], diff --git a/credentialsd-ui/src/dbus.rs b/credentialsd-ui/src/dbus.rs index aebe01f..859d3b6 100644 --- a/credentialsd-ui/src/dbus.rs +++ b/credentialsd-ui/src/dbus.rs @@ -19,7 +19,7 @@ use zbus::{ }; use credentialsd_common::{ - model::{Device, Operation, PortalBackendOptions, RequestId, UserInteractedEvent}, + model::{Device, Operation, PortalBackendOptions, UserInteractedEvent}, server::{BackgroundEvent, WindowHandle}, }; @@ -38,7 +38,6 @@ pub(crate) struct UiContext { parent_window: Option, origin: String, r#type: Operation, - request_id: RequestId, devices: Vec, app_id: String, app_display_name: String, @@ -59,7 +58,6 @@ impl CredentialPortalBackend { parent_window: Optional, origin: String, r#type: Operation, - request_id: RequestId, devices: Vec, app_id: String, app_pid: u32, @@ -134,7 +132,6 @@ impl CredentialPortalBackend { parent_window: parent_window.into(), origin, r#type, - request_id, devices, app_id, app_display_name, @@ -271,7 +268,6 @@ impl CeremonyObject { let req = ( ViewRequest { operation: self.ui_context.r#type.clone(), - id: self.ui_context.request_id, rp_id, requesting_app: RequestingApplication { path_or_app_id: self.ui_context.app_id.clone(), diff --git a/credentialsd-ui/src/main.rs b/credentialsd-ui/src/main.rs index 83472ad..031c8c2 100644 --- a/credentialsd-ui/src/main.rs +++ b/credentialsd-ui/src/main.rs @@ -6,7 +6,7 @@ mod gui; use std::error::Error; -use credentialsd_common::model::{Device, Operation, RequestId}; +use credentialsd_common::model::{Device, Operation}; use credentialsd_common::server::WindowHandle; use crate::dbus::CredentialPortalBackend; @@ -62,9 +62,6 @@ pub struct RequestingApplication { pub struct ViewRequest { pub operation: Operation, - /// ID of the request. - pub id: RequestId, - /// The RP ID pub rp_id: String, diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index 3f4659f..6b0e265 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -139,7 +139,6 @@ async fn handle, origin: String, r#type: Operation, - request_id: RequestId, devices: Vec, app_id: String, app_pid: u32, @@ -48,7 +47,6 @@ trait UiControlService2 { parent_window: Optional, origin: String, r#type: Operation, - request_id: RequestId, devices: Vec, app_id: String, app_pid: u32, @@ -117,7 +115,6 @@ impl UiController for UiControlServiceClient { parent_window: Option, origin: String, r#type: Operation, - request_id: RequestId, devices: Vec, app_id: String, app_pid: u32, @@ -138,7 +135,6 @@ impl UiController for UiControlServiceClient { parent_window.into(), origin, r#type, - request_id, devices, app_id, app_pid, From 8179638472b9975dc23e7022ce518abcd90a4bf8 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 12:23:09 -0500 Subject: [PATCH 2/7] common: Remove a bunch of unused stuff --- credentialsd-common/src/client.rs | 27 -- credentialsd-common/src/server.rs | 9 +- credentialsd-ui/src/gui/view_model/mod.rs | 2 +- credentialsd/src/credential_service/hybrid.rs | 111 -------- credentialsd/src/credential_service/mod.rs | 9 +- credentialsd/src/credential_service/nfc.rs | 3 + credentialsd/src/dbus/flow_control.rs | 241 ------------------ 7 files changed, 12 insertions(+), 390 deletions(-) diff --git a/credentialsd-common/src/client.rs b/credentialsd-common/src/client.rs index d5fe31b..8b13789 100644 --- a/credentialsd-common/src/client.rs +++ b/credentialsd-common/src/client.rs @@ -1,28 +1 @@ -use std::pin::Pin; -use futures_lite::Stream; - -use crate::{ - model::{Device, RequestId}, - server::BackgroundEvent, -}; - -/// Used for communication from trusted UI to credential service -pub trait FlowController { - fn get_available_public_key_devices( - &self, - ) -> impl Future, ()>> + Send; - - fn start_discovery(&mut self) -> impl Future> + Send; - fn subscribe( - &mut self, - ) -> impl Future< - Output = Result + Send + 'static>>, ()>, - > + Send; - fn enter_client_pin(&mut self, pin: String) -> impl Future> + Send; - fn select_credential( - &self, - credential_id: String, - ) -> impl Future> + Send; - fn cancel_request(&self, request_id: RequestId) -> impl Future> + Send; -} diff --git a/credentialsd-common/src/server.rs b/credentialsd-common/src/server.rs index fbe7d2e..4caa7d2 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -7,11 +7,11 @@ use serde::{ de::{DeserializeSeed, Error, Visitor}, }; use zvariant::{ - self, Array, DeserializeDict, DynamicDeserialize, Fd, NoneValue, Optional, OwnedFd, OwnedValue, + self, Array, DeserializeDict, DynamicDeserialize, Fd, NoneValue, OwnedFd, OwnedValue, SerializeDict, Signature, Str, Structure, StructureBuilder, Type, Value, signature::Fields, }; -use crate::model::{Device, Operation, UserInteractedEvent}; +use crate::model::UserInteractedEvent; const TAG_VALUE_SIGNATURE: &Signature = &Signature::Structure(Fields::Static { fields: &[&Signature::U32, &Signature::Variant], @@ -638,10 +638,7 @@ fn tag_value_to_struct(tag: u32, value: Option>) -> Structure<'static> mod test { use std::os::fd::{FromRawFd, OwnedFd}; - use zvariant::{ - Type, - serialized::{Context, Data, Format}, - }; + use zvariant::Type; use super::{BackgroundEvent, Credential}; diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index 25390a3..547ca79 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -13,7 +13,7 @@ use gettextrs::gettext; use serde::{Deserialize, Serialize}; use tracing::{error, info}; -use credentialsd_common::model::{Device, HybridState, Operation, Transport, ViewUpdate}; +use credentialsd_common::model::{Device, Operation, Transport, ViewUpdate}; use crate::{RequestingApplication, ViewRequest, client::FlowControlClient}; diff --git a/credentialsd/src/credential_service/hybrid.rs b/credentialsd/src/credential_service/hybrid.rs index b531f9f..0e892aa 100644 --- a/credentialsd/src/credential_service/hybrid.rs +++ b/credentialsd/src/credential_service/hybrid.rs @@ -293,114 +293,3 @@ async fn handle_hybrid_updates( } } } - -#[cfg(test)] -pub(super) mod test { - use std::task::Poll; - - use futures_lite::Stream; - use libwebauthn::{ - fido::{AuthenticatorData, AuthenticatorDataFlags}, - ops::webauthn::{Assertion, GetAssertionResponse}, - proto::ctap2::{Ctap2PublicKeyCredentialDescriptor, Ctap2Transport}, - }; - - use crate::model::CredentialRequest; - - use super::{HybridEvent, HybridHandler, HybridStateInternal}; - #[derive(Debug)] - pub struct DummyHybridHandler { - stream: DummyHybridStateStream, - } - - impl DummyHybridHandler { - #[cfg(test)] - pub fn new(states: Vec) -> Self { - Self { - stream: DummyHybridStateStream { states }, - } - } - } - - impl Default for DummyHybridHandler { - fn default() -> Self { - Self { - stream: DummyHybridStateStream::default(), - } - } - } - impl HybridHandler for DummyHybridHandler { - fn start( - &self, - _request: &CredentialRequest, - ) -> impl Stream + Send + Sized + Unpin + 'static { - self.stream.clone() - } - } - - #[derive(Clone, Debug)] - pub struct DummyHybridStateStream { - states: Vec, - } - - impl Default for DummyHybridStateStream { - fn default() -> Self { - let qr_code = String::from("FIDO:/078241338926040702789239694720083010994762289662861130514766991835876383562063181103169246410435938367110394959927031730060360967994421343201235185697538107096654083332"); - // SHA256("webauthn.io") - let rp_id_hash = [ - 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20, - 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0xb, - 0x60, 0x84, 0x1e, 0xf0, - ]; - - let auth_data = AuthenticatorData { - rp_id_hash, - flags: AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::USER_VERIFIED, - signature_count: 1, - attested_credential: None, - extensions: None, - raw: None, - }; - - let assertion = Assertion { - credential_id: Some(Ctap2PublicKeyCredentialDescriptor { - id: vec![0xca, 0xb1, 0xe].into(), - r#type: libwebauthn::proto::ctap2::Ctap2PublicKeyCredentialType::PublicKey, - transports: Some(vec![Ctap2Transport::Hybrid]), - }), - authenticator_data: auth_data, - signature: Vec::new(), - user: None, - credentials_count: Some(1), - user_selected: None, - unsigned_extensions_output: None, - }; - let response = GetAssertionResponse { - assertions: vec![assertion], - }; - DummyHybridStateStream { - states: vec![ - HybridStateInternal::Init(qr_code), - HybridStateInternal::Connecting, - HybridStateInternal::Completed(Box::new(response.into())), - ], - } - } - } - - impl Stream for DummyHybridStateStream { - type Item = HybridEvent; - - fn poll_next( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll> { - if self.states.len() == 0 { - Poll::Ready(None) - } else { - let state = (self.get_mut()).states.remove(0); - Poll::Ready(Some(HybridEvent { state })) - } - } - } -} diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index d95eeb3..74dacb3 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -82,7 +82,7 @@ pub struct CredentialService { ctx: Arc>>, hybrid_handler: Mutex, - nfc_handler: Mutex, + _nfc_handler: Mutex, usb_handler: Mutex, } @@ -94,7 +94,7 @@ impl ctx: Arc::new(Mutex::new(None)), hybrid_handler: Mutex::new(hybrid_handler), - nfc_handler: Mutex::new(nfc_handler), + _nfc_handler: Mutex::new(nfc_handler), usb_handler: Mutex::new(usb_handler), } } @@ -133,10 +133,10 @@ impl } } - async fn get_nfc_credential(&self) -> Pin + Send + 'static>> { + async fn _get_nfc_credential(&self) -> Pin + Send + 'static>> { let guard = self.ctx.lock().unwrap(); if let Some(RequestContext { ref request, .. }) = *guard { - let stream = self.nfc_handler.lock().unwrap().start(request); + let stream = self._nfc_handler.lock().unwrap().start(request); let ctx = self.ctx.clone(); Box::pin(NfcStateStream { inner: stream, ctx }) } else { @@ -324,6 +324,7 @@ where } } +#[expect(unused)] struct NfcStateStream { inner: H, ctx: Arc>>, diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index 33411c5..607247d 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -24,6 +24,7 @@ use crate::model::{CredentialRequest, GetAssertionResponseInternal}; use super::{AuthenticatorResponse, CredentialResponse}; pub(crate) trait NfcHandler { + #[expect(unused)] fn start( &self, request: &CredentialRequest, @@ -299,11 +300,13 @@ impl NfcHandler for InProcessNfcHandler { // this exists to prevent making NfcStateInternal type public to the whole crate. /// A message between NFC handler and credential service +#[expect(unused)] pub struct NfcEvent { pub(super) state: NfcStateInternal, } /// Used to share internal state between handler and credential service +#[expect(unused)] #[derive(Clone, Debug, Default)] pub(super) enum NfcStateInternal { /// Not polling for FIDO NFC device. diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index 6b0e265..160d0fc 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -318,244 +318,3 @@ impl CredentialRequestController for CredentialRequestControllerClient { response.map_err(|_| WebAuthnError::NotAllowedError) } } - -#[cfg(test)] -pub mod test { - use std::{ - error::Error, - fmt::Debug, - pin::Pin, - sync::{Arc, Mutex}, - }; - - use credentialsd_common::{ - client::FlowController, - model::{Device, RequestId}, - server::BackgroundEvent, - }; - use futures_lite::{Stream, StreamExt}; - use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; - - use crate::credential_service::{hybrid::HybridState, nfc::NfcState, ManageDevice, UsbState}; - - #[allow(clippy::enum_variant_names)] - #[derive(Debug)] - pub enum DummyFlowRequest { - EnterClientPin(String), - GetDevices, - GetCredential, - InitStream, - } - - // Clippy complains that these variant names have the same prefix, but that's - // intentional for now. - #[allow(clippy::enum_variant_names)] - pub enum DummyFlowResponse { - EnterClientPin(Result<(), ()>), - GetDevices(Vec), - GetCredential, - InitStream(Result + Send + 'static>>, ()>), - } - - impl Debug for DummyFlowResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::EnterClientPin(arg0) => f.debug_tuple("EnterClientPin").field(arg0).finish(), - Self::GetDevices(arg0) => f.debug_tuple("GetDevices").field(arg0).finish(), - Self::GetCredential => f.debug_tuple("GetCredential").finish(), - Self::InitStream(_) => f - .debug_tuple("InitStream") - .field(&String::from("")) - .finish(), - } - } - } - /// Represents a client for the UI to call methods on the credential service. - #[derive(Debug)] - pub struct DummyFlowClient { - tx: mpsc::Sender<(DummyFlowRequest, oneshot::Sender)>, - } - - impl DummyFlowClient { - async fn send(&self, request: DummyFlowRequest) -> Result { - let (response_tx, response_rx) = oneshot::channel(); - self.tx.send((request, response_tx)).await.unwrap(); - match response_rx.await { - Ok(response) => Ok(response), - Err(err) => { - tracing::error!("Failed to retrieve response from server: {:?}", err); - Err(()) - } - } - } - } - - impl FlowController for DummyFlowClient { - async fn get_available_public_key_devices(&self) -> Result, ()> { - let response = self.send(DummyFlowRequest::GetDevices).await.unwrap(); - if let DummyFlowResponse::GetDevices(devices) = response { - Ok(devices) - } else { - Err(()) - } - } - - async fn start_discovery(&mut self) -> Result<(), ()> { - if let Ok(DummyFlowResponse::GetCredential) = - self.send(DummyFlowRequest::GetCredential).await - { - Ok(()) - } else { - Err(()) - } - } - - async fn subscribe( - &mut self, - ) -> Result + Send + 'static>>, ()> { - if let Ok(DummyFlowResponse::InitStream(Ok(stream))) = - self.send(DummyFlowRequest::InitStream).await - { - Ok(stream) - } else { - Err(()) - } - } - - async fn enter_client_pin(&mut self, pin: String) -> Result<(), ()> { - if let Ok(DummyFlowResponse::EnterClientPin(Ok(()))) = - self.send(DummyFlowRequest::EnterClientPin(pin)).await - { - Ok(()) - } else { - Err(()) - } - } - - async fn select_credential(&self, _credential_id: String) -> Result<(), ()> { - todo!(); - } - - async fn cancel_request(&self, _request_id: RequestId) -> Result<(), ()> { - todo!() - } - } - - #[derive(Debug)] - pub struct DummyFlowServer - where - M: ManageDevice, - { - rx: mpsc::Receiver<(DummyFlowRequest, oneshot::Sender)>, - svc: Arc>, - bg_event_tx: Option>, - pin_tx: Arc>>>, - usb_event_forwarder_task: Arc>>, - nfc_event_forwarder_task: Arc>>, - hybrid_event_forwarder_task: Arc>>, - } - - impl DummyFlowServer { - pub fn new(svc: Arc>) -> (Self, DummyFlowClient) { - let (request_tx, request_rx) = mpsc::channel(32); - let server = Self { - rx: request_rx, - svc, - bg_event_tx: None, - pin_tx: Arc::new(AsyncMutex::new(None)), - usb_event_forwarder_task: Arc::new(Mutex::new(None)), - nfc_event_forwarder_task: Arc::new(Mutex::new(None)), - hybrid_event_forwarder_task: Arc::new(Mutex::new(None)), - }; - let client = DummyFlowClient { tx: request_tx }; - (server, client) - } - - pub async fn run(&mut self) { - while let Some((request, tx)) = self.rx.recv().await { - tracing::debug!(target: "DummyFlowServer", "Received message: {request:?}"); - let response = match request { - DummyFlowRequest::EnterClientPin(pin) => { - let rsp = self.enter_client_pin(pin).await; - DummyFlowResponse::EnterClientPin(rsp) - } - DummyFlowRequest::GetDevices => { - let rsp = self.get_available_public_key_devices().await.unwrap(); - DummyFlowResponse::GetDevices(rsp) - } - DummyFlowRequest::GetCredential => { - self.start_discovery().await.unwrap(); - DummyFlowResponse::GetCredential - } - DummyFlowRequest::InitStream => { - let rsp = self.subscribe().await; - DummyFlowResponse::InitStream(rsp) - } - }; - tx.send(response).unwrap() - } - } - - async fn get_available_public_key_devices(&self) -> Result, Box> { - tracing::debug!(target: "DummyFlowServer", "get_available_public_key_devices()"); - let devices = self - .svc - .lock() - .await - .get_available_public_key_devices() - .await - .map_err(|_| "Failed to get public key devices".to_string())?; - Ok(devices) - } - - async fn start_discovery(&mut self) -> Result<(), ()> { - unimplemented!(); - } - - async fn subscribe( - &mut self, - ) -> Result + Send + 'static>>, ()> { - let (tx, mut rx) = mpsc::channel(32); - self.bg_event_tx = Some(tx); - Ok(Box::pin(async_stream::stream! { - // TODO: we need to add a shutdown event that tells this stream - // to shut down when completed, failed or cancelled - while let Some(bg_event) = rx.recv().await { - yield bg_event - } - tracing::debug!("event stream ended"); - })) - } - - async fn enter_client_pin(&mut self, pin: String) -> 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) -> Result<(), ()> { - todo!(); - } - - async fn cancel_request(&self, _request_id: RequestId) -> Result<(), ()> { - todo!(); - } - } - - impl Drop for DummyFlowServer { - fn drop(&mut self) { - if let Some(task) = self.usb_event_forwarder_task.lock().unwrap().take() { - task.abort(); - } - - if let Some(task) = self.nfc_event_forwarder_task.lock().unwrap().take() { - task.abort(); - } - - if let Some(task) = self.hybrid_event_forwarder_task.lock().unwrap().take() { - task.abort(); - } - } - } -} From 34d92a6c5fbbf0bb05f89ae292e6f72bec4b623c Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 12:25:14 -0500 Subject: [PATCH 3/7] daemon: Move RequestId out of common --- credentialsd-common/src/model.rs | 3 --- credentialsd/src/credential_service/mod.rs | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index 55b84d5..c0e58a0 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -121,9 +121,6 @@ pub struct RequestingParty { pub origin: String, } -/// Identifier for a request to be used for cancellation. -pub type RequestId = u32; - // TODO: Move to credentialsd-ui #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ViewUpdate { diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index 74dacb3..45b86bd 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -20,7 +20,7 @@ use nfc::{NfcEvent, NfcHandler, NfcState, NfcStateInternal}; use tokio::sync::oneshot; use credentialsd_common::{ - model::{Device, Error as CredentialServiceError, RequestId, Transport}, + model::{Device, Error as CredentialServiceError, Transport}, server::BackgroundEvent, }; @@ -36,6 +36,9 @@ use self::{ pub use usb::UsbState; +/// Identifier for a request to be used for cancellation. +pub type RequestId = u32; + /// Process-wide in-memory store so a security key's pinUvAuthToken is reused across ceremonies. fn persistent_token_store() -> Arc { static STORE: OnceLock> = OnceLock::new(); From c409fc8d3391b12cded84a1d207754a07651a034 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 12:29:11 -0500 Subject: [PATCH 4/7] ui: Move ViewRequest out of common --- credentialsd-common/src/model.rs | 33 --------------------- credentialsd-ui/src/gui/mod.rs | 35 ++++++++++++++++++++++- credentialsd-ui/src/gui/view_model/mod.rs | 4 ++- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index c0e58a0..895a4bc 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -121,39 +121,6 @@ pub struct RequestingParty { pub origin: String, } -// TODO: Move to credentialsd-ui -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ViewUpdate { - SetTitle { - title: String, - subtitle: String, - qr_prompt: String, - usb_prompt: String, - }, - SetDevices(Vec), - // TODO: Fix this - SetCredentials(Vec), - - WaitingForDevice(Device), - SelectingDevice, - - NeedsPin { - attempts_left: Option, - }, - NeedsUserVerification { - attempts_left: Option, - }, - NeedsUserPresence, - - HybridNeedsQrCode(String), - HybridConnecting, - HybridConnected, - - Completed, - Cancelled, - Failed(String), -} - #[derive(Clone, Debug, Default)] pub enum HybridState { /// Default state, not listening for hybrid transport. diff --git a/credentialsd-ui/src/gui/mod.rs b/credentialsd-ui/src/gui/mod.rs index 3c56663..8f31bc6 100644 --- a/credentialsd-ui/src/gui/mod.rs +++ b/credentialsd-ui/src/gui/mod.rs @@ -5,7 +5,8 @@ use std::{sync::Arc, thread::JoinHandle}; use async_std::{channel::Receiver, sync::Mutex as AsyncMutex}; -use credentialsd_common::{model::ViewUpdate, server::WindowHandle}; +use credentialsd_common::model::Device; +use credentialsd_common::server::{Credential, WindowHandle}; use crate::{ViewRequest, client::FlowControlClient}; @@ -63,3 +64,35 @@ fn run_gui( async_std::task::block_on(event_loop.cancel()); } + +#[derive(Debug, Clone)] +pub enum ViewUpdate { + SetTitle { + title: String, + subtitle: String, + qr_prompt: String, + usb_prompt: String, + }, + SetDevices(Vec), + // TODO: Fix this + SetCredentials(Vec), + + WaitingForDevice(Device), + SelectingDevice, + + NeedsPin { + attempts_left: Option, + }, + NeedsUserVerification { + attempts_left: Option, + }, + NeedsUserPresence, + + HybridNeedsQrCode(String), + HybridConnecting, + HybridConnected, + + Completed, + Cancelled, + Failed(String), +} diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index 547ca79..9ee4537 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -13,10 +13,12 @@ use gettextrs::gettext; use serde::{Deserialize, Serialize}; use tracing::{error, info}; -use credentialsd_common::model::{Device, Operation, Transport, ViewUpdate}; +use credentialsd_common::model::{Device, Operation, Transport}; use crate::{RequestingApplication, ViewRequest, client::FlowControlClient}; +use super::ViewUpdate; + #[derive(Debug)] pub(crate) struct ViewModel { flow_controller: Arc>, From 7c655580745e6758dac96ebeae26caa58aa325d8 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 12:50:31 -0500 Subject: [PATCH 5/7] common: Remove unused enums --- credentialsd-common/src/model.rs | 133 ------------------ credentialsd/src/credential_service/hybrid.rs | 13 -- credentialsd/src/credential_service/nfc.rs | 32 ----- credentialsd/src/credential_service/usb.rs | 34 ----- 4 files changed, 212 deletions(-) diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index 895a4bc..2a1aa42 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -10,28 +10,6 @@ pub struct Credential { pub username: Option, } -/// Client Capabilities, as defined in -/// [WebAuthn](https://www.w3.org/TR/webauthn-3/#enumdef-clientcapability). -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict", rename_all = "camelCase")] -pub struct GetClientCapabilitiesResponse { - pub conditional_create: bool, - pub conditional_get: bool, - pub hybrid_transport: bool, - pub passkey_platform_authenticator: bool, - pub user_verifying_platform_authenticator: bool, - pub related_origins: bool, - pub signal_all_accepted_credentials: bool, - pub signal_current_user_details: bool, - pub signal_unknown_credential: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum CredentialType { - Passkey, - // Password, -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)] pub struct Device { pub id: String, @@ -115,117 +93,6 @@ impl Transport { } } -#[derive(Debug, Default, Clone, Serialize, Deserialize, Type)] -pub struct RequestingParty { - pub rp_id: String, - pub origin: String, -} - -#[derive(Clone, Debug, Default)] -pub enum HybridState { - /// Default state, not listening for hybrid transport. - #[default] - Idle, - - /// QR code flow is starting, awaiting QR code scan and BLE advert from phone. - Started(String), - - /// BLE advert received, connecting to caBLE tunnel with shared secret. - Connecting, - - /// Connected to device via caBLE tunnel. - Connected, - - /// Credential received over tunnel. - Completed, - - // This isn't actually sent from the server. - UserCancelled, - - /// Failed to receive a credential - Failed, -} - -/// Used to share public state between credential service and UI. -#[derive(Clone, Debug, Default)] -pub enum UsbState { - /// Not polling for FIDO USB device. - #[default] - Idle, - - /// Awaiting FIDO USB device to be plugged in. - Waiting, - - // When we encounter multiple devices, we let all of them blink and continue - // with the one that was tapped. - SelectingDevice, - - /// USB device connected, prompt user to tap - Connected, - - /// The device needs the PIN to be entered. - NeedsPin { - attempts_left: Option, - }, - - /// The device needs on-device user verification. - NeedsUserVerification { - attempts_left: Option, - }, - - /// The device needs evidence of user presence (e.g. touch) to release the credential. - NeedsUserPresence, - // TODO: implement cancellation - // This isn't actually sent from the server. - //UserCancelled, - /// Multiple credentials have been found and the user has to select which to use - SelectingCredential { - /// List of user-identities to decide which to use. - creds: Vec, - }, - - /// USB tapped, received credential - Completed, - - /// Interaction with the authenticator failed. - Failed(Error), -} - -/// Used to share public state between credential service and UI. -#[derive(Clone, Debug, Default)] -pub enum NfcState { - /// Not polling for FIDO NFC device. - #[default] - Idle, - - /// Awaiting FIDO NFC device to connect. - Waiting, - - /// USB device connected, prompt user to tap - Connected, - - /// The device needs the PIN to be entered. - NeedsPin { attempts_left: Option }, - - /// The device needs on-device user verification. - NeedsUserVerification { attempts_left: Option }, - - // TODO: implement cancellation - // This isn't actually sent from the server. - //UserCancelled, - /// Multiple credentials have been found and the user has to select which to use - SelectingCredential { - /// List of user-identities to decide which to use. - creds: Vec, - }, - - /// NFC tapped, received credential - Completed, - - /// Interaction with the authenticator failed. - Failed(Error), -} - pub enum UserInteractedEvent { /// Start discovery DiscoveryRequested, diff --git a/credentialsd/src/credential_service/hybrid.rs b/credentialsd/src/credential_service/hybrid.rs index 0e892aa..a5c3756 100644 --- a/credentialsd/src/credential_service/hybrid.rs +++ b/credentialsd/src/credential_service/hybrid.rs @@ -225,19 +225,6 @@ impl From for HybridState { } } -impl From for credentialsd_common::model::HybridState { - fn from(value: HybridState) -> Self { - match value { - HybridState::Init(qr_code) => credentialsd_common::model::HybridState::Started(qr_code), - HybridState::Connecting => credentialsd_common::model::HybridState::Connecting, - HybridState::Connected => credentialsd_common::model::HybridState::Connected, - HybridState::Completed => credentialsd_common::model::HybridState::Completed, - HybridState::UserCancelled => credentialsd_common::model::HybridState::UserCancelled, - HybridState::Failed => credentialsd_common::model::HybridState::Failed, - } - } -} - impl From<&HybridState> for BackgroundEvent { fn from(value: &HybridState) -> Self { match value { diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index 607247d..b514814 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -438,38 +438,6 @@ impl From for NfcState { } } -impl From for credentialsd_common::model::NfcState { - fn from(value: NfcState) -> Self { - Self::from(&value) - } -} -impl From<&NfcState> for credentialsd_common::model::NfcState { - fn from(value: &NfcState) -> Self { - match value { - NfcState::Idle => credentialsd_common::model::NfcState::Idle, - NfcState::Waiting => credentialsd_common::model::NfcState::Waiting, - NfcState::Connected => credentialsd_common::model::NfcState::Connected, - NfcState::NeedsPin { attempts_left, .. } => { - credentialsd_common::model::NfcState::NeedsPin { - attempts_left: *attempts_left, - } - } - NfcState::NeedsUserVerification { attempts_left } => { - credentialsd_common::model::NfcState::NeedsUserVerification { - attempts_left: *attempts_left, - } - } - NfcState::SelectingCredential { creds, .. } => { - credentialsd_common::model::NfcState::SelectingCredential { - creds: creds.to_owned(), - } - } - NfcState::Completed => credentialsd_common::model::NfcState::Completed, - NfcState::Failed(err) => credentialsd_common::model::NfcState::Failed(err.to_owned()), - } - } -} - impl From<&NfcState> for BackgroundEvent { fn from(value: &NfcState) -> Self { match value { diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index 70e1524..8c6311a 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -539,40 +539,6 @@ impl From for UsbState { } } -impl From for credentialsd_common::model::UsbState { - fn from(value: UsbState) -> Self { - Self::from(&value) - } -} -impl From<&UsbState> for credentialsd_common::model::UsbState { - fn from(value: &UsbState) -> Self { - match value { - UsbState::Idle => credentialsd_common::model::UsbState::Idle, - UsbState::Waiting => credentialsd_common::model::UsbState::Waiting, - UsbState::SelectingDevice => credentialsd_common::model::UsbState::SelectingDevice, - UsbState::Connected => credentialsd_common::model::UsbState::Connected, - UsbState::NeedsPin { attempts_left, .. } => { - credentialsd_common::model::UsbState::NeedsPin { - attempts_left: *attempts_left, - } - } - UsbState::NeedsUserVerification { attempts_left } => { - credentialsd_common::model::UsbState::NeedsUserVerification { - attempts_left: *attempts_left, - } - } - UsbState::NeedsUserPresence => credentialsd_common::model::UsbState::NeedsUserPresence, - UsbState::SelectingCredential { creds, .. } => { - credentialsd_common::model::UsbState::SelectingCredential { - creds: creds.to_owned(), - } - } - UsbState::Completed => credentialsd_common::model::UsbState::Completed, - UsbState::Failed(err) => credentialsd_common::model::UsbState::Failed(err.to_owned()), - } - } -} - impl From<&UsbState> for BackgroundEvent { fn from(value: &UsbState) -> Self { match value { From 36e07cfedaf8f2d0e588679aa56efadb726b402e Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 12:50:31 -0500 Subject: [PATCH 6/7] daemon: Move *CredentialRequest/Response out of common --- credentialsd-common/src/server.rs | 90 +------------------------- credentialsd/src/gateway/dbus.rs | 13 ++-- credentialsd/src/gateway/mod.rs | 102 +++++++++++++++++++++++++++--- credentialsd/src/gateway/util.rs | 14 ++-- 4 files changed, 108 insertions(+), 111 deletions(-) diff --git a/credentialsd-common/src/server.rs b/credentialsd-common/src/server.rs index 4caa7d2..06d6b3f 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -1,6 +1,6 @@ //! Types for serializing across D-Bus instances -use std::{collections::HashMap, fmt::Display}; +use std::fmt::Display; use serde::{ Deserialize, Serialize, @@ -272,55 +272,6 @@ impl<'de> Deserialize<'de> for BackgroundEvent { } } -#[derive(Clone, Debug, DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct CreateCredentialRequest { - pub origin: Option, - pub is_same_origin: Option, - #[zvariant(rename = "type")] - pub r#type: String, - #[zvariant(rename = "publicKey")] - pub public_key: Option, -} - -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct CreateCredentialResponse { - #[zvariant(rename = "type")] - r#type: String, - public_key: Option, -} - -impl NoneValue for CreateCredentialResponse { - type NoneType = HashMap; - - fn null_value() -> Self::NoneType { - HashMap::new() - } -} - -#[derive(Clone, Debug, DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct CreatePublicKeyCredentialRequest { - pub request_json: String, -} - -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct CreatePublicKeyCredentialResponse { - pub registration_response_json: String, -} - -impl From for CreateCredentialResponse { - fn from(response: CreatePublicKeyCredentialResponse) -> Self { - CreateCredentialResponse { - // TODO: Decide on camelCase or kebab-case for cred types - r#type: "public-key".to_string(), - public_key: Some(response), - } - } -} - #[derive(Debug, Clone, SerializeDict, DeserializeDict, PartialEq, Type, Value)] #[zvariant(signature = "dict")] pub struct Credential { @@ -378,45 +329,6 @@ impl TryFrom<&Value<'_>> for crate::model::Error { } } -#[derive(Clone, Debug, DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct GetCredentialRequest { - pub origin: Option, - pub is_same_origin: Option, - #[zvariant(rename = "publicKey")] - pub public_key: Option, -} - -#[derive(Clone, Debug, DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct GetPublicKeyCredentialRequest { - pub request_json: String, -} - -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct GetCredentialResponse { - #[zvariant(rename = "type")] - r#type: String, - public_key: Option, -} - -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct GetPublicKeyCredentialResponse { - pub authentication_response_json: String, -} - -impl From for GetCredentialResponse { - fn from(response: GetPublicKeyCredentialResponse) -> Self { - GetCredentialResponse { - // TODO: Decide on camelCase or kebab-case for cred types - r#type: "public-key".to_string(), - public_key: Some(response), - } - } -} - impl Type for UserInteractedEvent { const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE; } diff --git a/credentialsd/src/gateway/dbus.rs b/credentialsd/src/gateway/dbus.rs index ca5a721..dccf6b9 100644 --- a/credentialsd/src/gateway/dbus.rs +++ b/credentialsd/src/gateway/dbus.rs @@ -10,16 +10,17 @@ use zbus::{ Connection, DBusError, }; -use credentialsd_common::{ - model::WebAuthnError, - server::{ +use credentialsd_common::{model::WebAuthnError, server::WindowHandle}; + +use crate::{ + gateway::{ CreateCredentialRequest, CreateCredentialResponse, CreatePublicKeyCredentialRequest, - GetCredentialRequest, GetCredentialResponse, GetPublicKeyCredentialRequest, WindowHandle, + GetCredentialRequest, GetCredentialResponse, GetPublicKeyCredentialRequest, }, + webauthn::AppId, + DBUS_SERVICE_NAME, }; -use crate::{webauthn::AppId, DBUS_SERVICE_NAME}; - use super::{check_origin_from_app, GatewayService, RequestContext}; pub const PORTAL_SERVICE_PATH: &str = "/org/freedesktop/portal/desktop"; diff --git a/credentialsd/src/gateway/mod.rs b/credentialsd/src/gateway/mod.rs index d662762..ba38df3 100644 --- a/credentialsd/src/gateway/mod.rs +++ b/credentialsd/src/gateway/mod.rs @@ -5,19 +5,17 @@ mod dbus; mod util; use std::{ + collections::HashMap, path::{Path, PathBuf}, sync::Arc, }; -use credentialsd_common::{ - model::WebAuthnError, - server::{ - CreateCredentialRequest, CreateCredentialResponse, GetCredentialRequest, - GetCredentialResponse, WindowHandle, - }, -}; +use credentialsd_common::{model::WebAuthnError, server::WindowHandle}; use tokio::sync::Mutex as AsyncMutex; -use zbus::Connection; +use zbus::{ + zvariant::{DeserializeDict, NoneValue, OwnedValue, SerializeDict, Type}, + Connection, +}; use crate::{ dbus::CredentialRequestController, @@ -345,6 +343,94 @@ fn check_origin_from_privileged_client( } } +#[derive(Clone, Debug, DeserializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct GetCredentialRequest { + pub origin: Option, + pub is_same_origin: Option, + #[zvariant(rename = "publicKey")] + pub public_key: Option, +} + +#[derive(Clone, Debug, DeserializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct GetPublicKeyCredentialRequest { + pub request_json: String, +} + +#[derive(SerializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct GetCredentialResponse { + #[zvariant(rename = "type")] + r#type: String, + public_key: Option, +} + +#[derive(SerializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct GetPublicKeyCredentialResponse { + pub authentication_response_json: String, +} + +impl From for GetCredentialResponse { + fn from(response: GetPublicKeyCredentialResponse) -> Self { + GetCredentialResponse { + // TODO: Decide on camelCase or kebab-case for cred types + r#type: "public-key".to_string(), + public_key: Some(response), + } + } +} + +#[derive(Clone, Debug, DeserializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct CreateCredentialRequest { + pub origin: Option, + pub is_same_origin: Option, + #[zvariant(rename = "type")] + pub r#type: String, + #[zvariant(rename = "publicKey")] + pub public_key: Option, +} + +#[derive(SerializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct CreateCredentialResponse { + #[zvariant(rename = "type")] + r#type: String, + public_key: Option, +} + +impl NoneValue for CreateCredentialResponse { + type NoneType = HashMap; + + fn null_value() -> Self::NoneType { + HashMap::new() + } +} + +#[derive(Clone, Debug, DeserializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct CreatePublicKeyCredentialRequest { + pub request_json: String, +} + +#[derive(SerializeDict, Type)] +#[zvariant(signature = "dict")] +pub struct CreatePublicKeyCredentialResponse { + pub registration_response_json: String, +} + +impl From for CreateCredentialResponse { + fn from(response: CreatePublicKeyCredentialResponse) -> Self { + CreateCredentialResponse { + // TODO: Decide on camelCase or kebab-case for cred types + r#type: "public-key".to_string(), + public_key: Some(response), + } + } +} + #[cfg(test)] mod test { use credentialsd_common::model::WebAuthnError; diff --git a/credentialsd/src/gateway/util.rs b/credentialsd/src/gateway/util.rs index 8fb00a1..892bfc3 100644 --- a/credentialsd/src/gateway/util.rs +++ b/credentialsd/src/gateway/util.rs @@ -2,13 +2,7 @@ //! //! Types shared between components within this service belong in credentialsd_common::model. -use credentialsd_common::{ - model::WebAuthnError, - server::{ - CreateCredentialRequest, CreatePublicKeyCredentialResponse, GetCredentialRequest, - GetPublicKeyCredentialResponse, - }, -}; +use credentialsd_common::model::WebAuthnError; use libwebauthn::ops::webauthn::idl::origin::{ Origin as LibwebauthnOrigin, RequestOrigin as LibwebauthnRequestOrigin, }; @@ -18,10 +12,14 @@ use libwebauthn::ops::webauthn::{ ReqwestRelatedOriginsSource, }; -use crate::model::{GetAssertionResponseInternal, MakeCredentialResponseInternal}; +use crate::gateway::{GetCredentialRequest, GetPublicKeyCredentialResponse}; use crate::webauthn::{ GetAssertionRequest, MakeCredentialRequest, NavigationContext, Origin, WebAuthnIDLResponse, }; +use crate::{ + gateway::{CreateCredentialRequest, CreatePublicKeyCredentialResponse}, + model::{GetAssertionResponseInternal, MakeCredentialResponseInternal}, +}; impl TryFrom<&Origin> for LibwebauthnOrigin { type Error = WebAuthnError; From 0405896f309a9423658ea6e784e2fafee032d897 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 10 Jul 2026 13:01:34 -0500 Subject: [PATCH 7/7] daemon: Move WebAuthnError out of common --- credentialsd-common/src/model.rs | 54 ----------------------- credentialsd/src/dbus/flow_control.rs | 3 +- credentialsd/src/gateway/dbus.rs | 4 +- credentialsd/src/gateway/mod.rs | 62 +++++++++++++++++++++++++-- credentialsd/src/gateway/util.rs | 13 +++--- 5 files changed, 68 insertions(+), 68 deletions(-) diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index 2a1aa42..3eeea36 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -159,57 +159,3 @@ impl Display for Error { } } } - -#[derive(Debug)] -pub enum WebAuthnError { - /// The ceremony was cancelled by an AbortController. See § 5.6 Abort - /// Operations with AbortSignal and § 1.3.4 Aborting Authentication - /// Operations. - AbortError, - - /// Either `residentKey` was set to required and no available authenticator - /// supported resident keys, or `userVerification` was set to required and no - /// available authenticator could perform user verification. - ConstraintError, - - /// The authenticator used in the ceremony recognized an entry in - /// `excludeCredentials` after the user consented to registering a credential. - InvalidStateError, - - /// No entry in `pubKeyCredParams` had a type property of `public-key`, or the - /// authenticator did not support any of the signature algorithms specified - /// in `pubKeyCredParams`. - NotSupportedError, - - /// The effective domain was not a valid domain, or `rp.id` was not equal to - /// or a registrable domain suffix of the effective domain. In the latter - /// case, the client does not support related origin requests or the related - /// origins validation procedure failed. - SecurityError, - - /// A catch-all error covering a wide range of possible reasons, including - /// common ones like the user canceling out of the ceremony. Some of these - /// causes are documented throughout this spec, while others are - /// client-specific. - NotAllowedError, - - /// The options argument was not a valid `CredentialCreationOptions` value, or - /// the value of `user.id` was empty or was longer than 64 bytes. - TypeError, -} - -impl std::error::Error for WebAuthnError {} - -impl Display for WebAuthnError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - WebAuthnError::AbortError => "Operation was aborted by client.", - WebAuthnError::ConstraintError => "Resident key or user verification requirement was not able to be met.", - WebAuthnError::InvalidStateError => "A user consented to create a new credential after trying to use an authenticator with a previously registered credential.", - WebAuthnError::NotSupportedError => "Operation parameters are not supported.", - WebAuthnError::SecurityError => "Validation of the client context for given RP ID failed.", - WebAuthnError::NotAllowedError => "An unspecified error occurred, and the operation is not allowed to continue.", - WebAuthnError::TypeError => "Invalid parameters specified.", - }) - } -} diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index 160d0fc..045a30f 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -13,7 +13,6 @@ use credentialsd_common::{ memfd::read_secret, model::{ Error as CredentialServiceError, Operation, PortalBackendOptions, UserInteractedEvent, - WebAuthnError, }, }; use futures_lite::{Stream, StreamExt}; @@ -24,7 +23,6 @@ use tokio::task::AbortHandle; use zbus::connection::Connection; use zbus::zvariant::OwnedObjectPath; -use crate::dbus::ui_control::Ceremony; use crate::dbus::UiControlServiceClient; use crate::{ credential_service::UsbState, @@ -35,6 +33,7 @@ use crate::{ credential_service::{nfc::NfcState, DeviceStateUpdate, ManageDevice}, model::ClientDetails, }; +use crate::{dbus::ui_control::Ceremony, gateway::WebAuthnError}; pub struct UiRequestContext { request: CredentialRequest, diff --git a/credentialsd/src/gateway/dbus.rs b/credentialsd/src/gateway/dbus.rs index dccf6b9..b4e4355 100644 --- a/credentialsd/src/gateway/dbus.rs +++ b/credentialsd/src/gateway/dbus.rs @@ -10,12 +10,12 @@ use zbus::{ Connection, DBusError, }; -use credentialsd_common::{model::WebAuthnError, server::WindowHandle}; +use credentialsd_common::server::WindowHandle; use crate::{ gateway::{ CreateCredentialRequest, CreateCredentialResponse, CreatePublicKeyCredentialRequest, - GetCredentialRequest, GetCredentialResponse, GetPublicKeyCredentialRequest, + GetCredentialRequest, GetCredentialResponse, GetPublicKeyCredentialRequest, WebAuthnError, }, webauthn::AppId, DBUS_SERVICE_NAME, diff --git a/credentialsd/src/gateway/mod.rs b/credentialsd/src/gateway/mod.rs index ba38df3..149a433 100644 --- a/credentialsd/src/gateway/mod.rs +++ b/credentialsd/src/gateway/mod.rs @@ -6,11 +6,12 @@ mod util; use std::{ collections::HashMap, + fmt::Display, path::{Path, PathBuf}, sync::Arc, }; -use credentialsd_common::{model::WebAuthnError, server::WindowHandle}; +use credentialsd_common::server::WindowHandle; use tokio::sync::Mutex as AsyncMutex; use zbus::{ zvariant::{DeserializeDict, NoneValue, OwnedValue, SerializeDict, Type}, @@ -431,13 +432,66 @@ impl From for CreateCredentialResponse { } } +#[derive(Debug)] +pub enum WebAuthnError { + /// The ceremony was cancelled by an AbortController. See § 5.6 Abort + /// Operations with AbortSignal and § 1.3.4 Aborting Authentication + /// Operations. + AbortError, + + /// Either `residentKey` was set to required and no available authenticator + /// supported resident keys, or `userVerification` was set to required and no + /// available authenticator could perform user verification. + ConstraintError, + + /// The authenticator used in the ceremony recognized an entry in + /// `excludeCredentials` after the user consented to registering a credential. + InvalidStateError, + + /// No entry in `pubKeyCredParams` had a type property of `public-key`, or the + /// authenticator did not support any of the signature algorithms specified + /// in `pubKeyCredParams`. + NotSupportedError, + + /// The effective domain was not a valid domain, or `rp.id` was not equal to + /// or a registrable domain suffix of the effective domain. In the latter + /// case, the client does not support related origin requests or the related + /// origins validation procedure failed. + SecurityError, + + /// A catch-all error covering a wide range of possible reasons, including + /// common ones like the user canceling out of the ceremony. Some of these + /// causes are documented throughout this spec, while others are + /// client-specific. + NotAllowedError, + + /// The options argument was not a valid `CredentialCreationOptions` value, or + /// the value of `user.id` was empty or was longer than 64 bytes. + TypeError, +} + +impl std::error::Error for WebAuthnError {} + +impl Display for WebAuthnError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + WebAuthnError::AbortError => "Operation was aborted by client.", + WebAuthnError::ConstraintError => "Resident key or user verification requirement was not able to be met.", + WebAuthnError::InvalidStateError => "A user consented to create a new credential after trying to use an authenticator with a previously registered credential.", + WebAuthnError::NotSupportedError => "Operation parameters are not supported.", + WebAuthnError::SecurityError => "Validation of the client context for given RP ID failed.", + WebAuthnError::NotAllowedError => "An unspecified error occurred, and the operation is not allowed to continue.", + WebAuthnError::TypeError => "Invalid parameters specified.", + }) + } +} + #[cfg(test)] mod test { - use credentialsd_common::model::WebAuthnError; - use crate::webauthn::{NavigationContext, Origin}; - use super::check_origin_from_privileged_client; + use super::{check_origin_from_privileged_client, WebAuthnError}; + fn check_same_origin(origin: &str) -> Result { let origin = origin.parse().unwrap(); check_origin_from_privileged_client(&origin, None) diff --git a/credentialsd/src/gateway/util.rs b/credentialsd/src/gateway/util.rs index 892bfc3..cdfd026 100644 --- a/credentialsd/src/gateway/util.rs +++ b/credentialsd/src/gateway/util.rs @@ -2,7 +2,6 @@ //! //! Types shared between components within this service belong in credentialsd_common::model. -use credentialsd_common::model::WebAuthnError; use libwebauthn::ops::webauthn::idl::origin::{ Origin as LibwebauthnOrigin, RequestOrigin as LibwebauthnRequestOrigin, }; @@ -12,13 +11,15 @@ use libwebauthn::ops::webauthn::{ ReqwestRelatedOriginsSource, }; -use crate::gateway::{GetCredentialRequest, GetPublicKeyCredentialResponse}; -use crate::webauthn::{ - GetAssertionRequest, MakeCredentialRequest, NavigationContext, Origin, WebAuthnIDLResponse, -}; use crate::{ - gateway::{CreateCredentialRequest, CreatePublicKeyCredentialResponse}, + gateway::{ + CreateCredentialRequest, CreatePublicKeyCredentialResponse, GetCredentialRequest, + GetPublicKeyCredentialResponse, WebAuthnError, + }, model::{GetAssertionResponseInternal, MakeCredentialResponseInternal}, + webauthn::{ + GetAssertionRequest, MakeCredentialRequest, NavigationContext, Origin, WebAuthnIDLResponse, + }, }; impl TryFrom<&Origin> for LibwebauthnOrigin {