From 2399214f0dfa5a1af422f1e7b0814c0147da4ddd Mon Sep 17 00:00:00 2001 From: Dhanuka Warusadura Date: Sun, 19 Jan 2025 19:50:23 +0530 Subject: [PATCH] Add prompt implementation for service method unlock Signed-off-by: Dhanuka Warusadura --- server/src/gnome/prompter.rs | 117 ++++++++++++++++++++++++++++++++++- server/src/service.rs | 22 ++++++- 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/server/src/gnome/prompter.rs b/server/src/gnome/prompter.rs index 20c9bbed..cefab0ae 100644 --- a/server/src/gnome/prompter.rs +++ b/server/src/gnome/prompter.rs @@ -55,6 +55,24 @@ impl Properties { cancel_label: Some("Cancel".to_string()), } } + + fn for_unlock() -> Self { + Self { + title: Some("Unlock Keyring".to_string()), + choice_label: None, + description: Some( + "An application wants access to the keyring 'login', but it is locked".to_string(), + ), + message: Some("Authentication required".to_string()), + caller_window: None, + warning: None, + password_new: Some(false), + password_strength: Some(0), + choice_chosen: Some(false), + continue_label: Some("Unlock".to_string()), + cancel_label: Some("Cancel".to_string()), + } + } } #[derive(Debug, Type)] @@ -219,7 +237,104 @@ impl PrompterCallback { } } } - PromptRole::Unlock => todo!(), + PromptRole::Unlock => { + match reply { + Reply::Empty => { + // First PromptReady call + let secret_exchange = SecretExchange::new().map_err(|err| { + ServiceError::ZBus(zbus::Error::FDO(Box::new( + zbus::fdo::Error::Failed(format!( + "Failed to generate SecretExchange {err}." + )), + ))) + })?; + let daemon_exchange = secret_exchange.begin(); + let aes_key = + secret_exchange + .create_shared_secret(exchange) + .map_err(|err| { + ServiceError::ZBus(zbus::Error::FDO(Box::new( + zbus::fdo::Error::Failed(format!( + "Failed to generate AES key for SecretExchange {err}." + )), + ))) + })?; + self.service.set_secret_exchange_key(aes_key).await; + + let properties = Properties::for_unlock(); + let path = self.path.clone(); + + tokio::spawn(PrompterCallback::perform_prompt( + connection.clone(), + path, + PromptType::Confirm, + properties, + daemon_exchange, + )); + } + Reply::No => { + // Second PromptReady call and the prompt is dismissed + tracing::debug!("Prompt is being dismissed."); + + tokio::spawn(PrompterCallback::stop_prompting( + connection.clone(), + self.path.clone(), + )); + + let signal_emitter = self.service.signal_emitter(prompt.path().clone())?; + let result = Value::new::>(vec![]) + .try_to_owned() + .unwrap(); + + tokio::spawn(PrompterCallback::prompt_completed( + signal_emitter, + true, + result, + )); + } + Reply::Yes => { + // Second PromptReady call with the final exchange. + // Verify secret + if let Some(secret) = SecretExchange::retrieve_secret( + exchange, + &self.service.secret_exchange_key().await, + ) { + match oo7::file::Keyring::open("login", secret).await { + Ok(_) => { + tracing::debug!("Login keyring secret matches."); + } + Err(oo7::file::Error::IncorrectSecret) => { + tracing::error!("Login keyring incorrect secret."); + // TODO: offer the prompt again to re-enter the password. + // TODO: limit number attempts. + } + Err(_) => todo!(), + } + } + + let service = self.service.clone(); + let objects = prompt.objects().clone(); + let result = Value::new(&objects).try_to_owned().unwrap(); + + tokio::spawn(async move { + let _ = service.set_locked(true, &objects, false).await; + }); + + tokio::spawn(PrompterCallback::stop_prompting( + connection.clone(), + self.path.clone(), + )); + + let signal_emitter = self.service.signal_emitter(prompt.path().clone())?; + + tokio::spawn(PrompterCallback::prompt_completed( + signal_emitter, + false, + result, + )); + } + } + } PromptRole::CreateCollection => todo!(), }; diff --git a/server/src/service.rs b/server/src/service.rs index 1f7267ed..793f5b1a 100644 --- a/server/src/service.rs +++ b/server/src/service.rs @@ -36,6 +36,8 @@ pub struct Service { session_index: Arc>, prompts: Arc>>, prompt_index: Arc>, + // SecretExchange aes_key + secret_exchange_key: Arc>, } #[zbus::interface(name = "org.freedesktop.Secret.Service")] @@ -137,8 +139,17 @@ impl Service { pub async fn unlock( &self, objects: Vec, + #[zbus(object_server)] object_server: &zbus::ObjectServer, ) -> Result<(Vec, OwnedObjectPath), ServiceError> { - let (unlocked, _not_unlocked) = self.set_locked(false, &objects, false).await?; + let (unlocked, not_unlocked) = self.set_locked(false, &objects, false).await?; + if !not_unlocked.is_empty() { + let prompt = Prompt::new(self.clone(), not_unlocked, PromptRole::Unlock).await; + self.prompts.lock().await.push(prompt.clone()); + let path = prompt.path().clone(); + + object_server.at(&path, prompt).await?; + return Ok((unlocked, path)); + } Ok((unlocked, OwnedObjectPath::default())) } @@ -291,6 +302,7 @@ impl Service { session_index: Default::default(), prompts: Default::default(), prompt_index: Default::default(), + secret_exchange_key: Default::default(), }; object_server @@ -452,4 +464,12 @@ impl Service { // it should be cleaned up afterwards. self.prompts.lock().await.pop(); } + + pub async fn set_secret_exchange_key(&self, aes_key: String) { + *self.secret_exchange_key.write().await = aes_key; + } + + pub async fn secret_exchange_key(&self) -> String { + self.secret_exchange_key.read().await.clone() + } }