From 7e128dd2de6b72035b2dbc144b1cfa4f850bf2e3 Mon Sep 17 00:00:00 2001 From: "Maximiliano Sandoval R." Date: Sun, 26 Nov 2023 00:12:33 +0100 Subject: [PATCH] WIP: portal impl --- .gitignore | 1 + oo7-portal-impl/Cargo.toml | 17 ++ oo7-portal-impl/data/oo7-portal-impl.portal | 4 + oo7-portal-impl/data/oo7-portal-impl.service | 3 + oo7-portal-impl/src/main.rs | 225 +++++++++++++++++++ 5 files changed, 250 insertions(+) create mode 100644 oo7-portal-impl/Cargo.toml create mode 100644 oo7-portal-impl/data/oo7-portal-impl.portal create mode 100644 oo7-portal-impl/data/oo7-portal-impl.service create mode 100644 oo7-portal-impl/src/main.rs diff --git a/.gitignore b/.gitignore index 96ef6c0b9..40ba2e26a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +/oo7-portal-impl/target Cargo.lock diff --git a/oo7-portal-impl/Cargo.toml b/oo7-portal-impl/Cargo.toml new file mode 100644 index 000000000..31880f9b0 --- /dev/null +++ b/oo7-portal-impl/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "oo7-portal-impl" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-std = { version = "1.11", features = [ "attributes" ] } +futures-channel = "0.3" +oo7 = { path = "../" } +ring = "0.17.5" +secrecy = { version = "0.8", features = [ "alloc" ] } +serde = {version = "1.0", features = ["derive"]} +tracing = "0.1" +tracing-subscriber = "0.3" +zbus = "3.5.0" +zeroize = { version = "1", features = ["zeroize_derive"] } +futures = "0.3" diff --git a/oo7-portal-impl/data/oo7-portal-impl.portal b/oo7-portal-impl/data/oo7-portal-impl.portal new file mode 100644 index 000000000..761808852 --- /dev/null +++ b/oo7-portal-impl/data/oo7-portal-impl.portal @@ -0,0 +1,4 @@ +[portal] +DBusName=org.freedesktop.impl.portal.desktop.oo7 +Interfaces=org.freedesktop.impl.portal.Secret +UseIn=gnome diff --git a/oo7-portal-impl/data/oo7-portal-impl.service b/oo7-portal-impl/data/oo7-portal-impl.service new file mode 100644 index 000000000..e69169efc --- /dev/null +++ b/oo7-portal-impl/data/oo7-portal-impl.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.impl.portal.Secret +Exec=@bindir@/oo7-portal-impl diff --git a/oo7-portal-impl/src/main.rs b/oo7-portal-impl/src/main.rs new file mode 100644 index 000000000..7ff18f29a --- /dev/null +++ b/oo7-portal-impl/src/main.rs @@ -0,0 +1,225 @@ +use std::borrow::BorrowMut; +use std::collections::HashMap; +use std::io::Write; +use std::os::fd::AsRawFd; +use std::os::fd::FromRawFd; +use std::sync::{Arc, Mutex}; + +use futures::FutureExt; +use oo7::dbus::{Algorithm, Service}; +use ring::rand::SecureRandom; +use serde::Serialize; +use std::future::pending; +use zbus::dbus_interface; +use zbus::zvariant; +use zbus::zvariant::Type; +use zvariant::OwnedObjectPath; + +const PORTAL_SECRET_SIZE: usize = 64; +const NAME: &str = "org.freedesktop.impl.portal.desktop.oo7"; +const PATH: &str = "/org/freedesktop/portal/desktop"; + +#[derive(Serialize, PartialEq, Eq, Debug, Type)] +#[doc(hidden)] +enum ResponseType { + Success = 0, + Cancelled = 1, + Other = 2, +} + +#[derive(zbus::DBusError, Debug)] +enum Error { + Msg(String), +} + +impl Error { + fn new(msg: &str) -> Error { + Error::Msg(msg.to_string()) + } +} + +impl From for Error { + fn from(err: oo7::dbus::Error) -> Self { + Error::new(&err.to_string()) + } +} + +struct Secret; + +#[dbus_interface(name = "org.freedesktop.impl.portal.Secret")] +impl Secret { + #[dbus_interface(property, name = "version")] + fn version(&self) -> u32 { + 0 + } + + #[dbus_interface(out_args("response", "results"))] + async fn retrieve_secret( + &self, + #[zbus(object_server)] object_server: &zbus::ObjectServer, + handle: OwnedObjectPath, + app_id: &str, + fd: zvariant::Fd, + _options: HashMap<&str, zvariant::Value<'_>>, + ) -> Result<(ResponseType, HashMap<&str, zvariant::OwnedValue>), Error> { + tracing::debug!("Got request from {app_id} with options: {_options:?}"); + + let (sender, receiver) = futures_channel::oneshot::channel(); + + if let Err(err) = Request::serve(object_server, handle.clone(), sender).await { + tracing::error!("Could not register object {handle}: {err}"); + } + + let fut_1 = async move { + let res = match retrieve_secret_inner(app_id, fd).await { + Ok(res) => Ok((ResponseType::Success, res)), + Err(err) => { + tracing::error!("could not retrieve secret: {err}"); + Ok((ResponseType::Other, HashMap::new())) + } + }; + + // We do not accept Close request anymore here. + tracing::debug!("Object {handle} handled"); + object_server + .remove::(&handle) + .await + .unwrap(); + + res + }; + + let fut_2 = async move { + receiver.await.unwrap(); + Ok((ResponseType::Cancelled, HashMap::new())) + }; + + let t1 = fut_1.fuse(); + let t2 = fut_2.fuse(); + + futures::pin_mut!(t1, t2); + + futures::select! { + fut_1_res = t1 => fut_1_res, + fut_2_res = t2 => fut_2_res, + } + } +} + +fn generate_secret() -> Result>, Error> { + let mut secret = [0; PORTAL_SECRET_SIZE]; + let rand = ring::rand::SystemRandom::new(); + rand.fill(secret.borrow_mut()) + .map_err(|err| Error::new(&err.to_string()))?; + Ok(zeroize::Zeroizing::new(secret.to_vec())) +} + +async fn retrieve_secret_inner( + app_id: &str, + fd: zvariant::Fd, +) -> Result, Error> { + let service = Service::new(Algorithm::Encrypted) + .await + .or(Service::new(Algorithm::Plain).await)?; + let collection = service.default_collection().await?; + let attributes = HashMap::from([("app_id", app_id)]); + + let secret = if let Some(item) = collection + .search_items(attributes.clone()) + .await + .map_err(|err| Error::new(&err.to_string()))? + .first() + { + item.secret().await? + } else { + tracing::debug!("Could not find secret for {app_id}, creating one"); + let secret = generate_secret()?; + collection + .create_item( + &format!("Secret Portal token for {app_id}"), + attributes, + &secret, + true, + // TODO Find a better one. + "text/plain", + ) + .await?; + + secret + }; + + // Write the secret to the FD. + let raw_fd = fd.as_raw_fd(); + let mut stream = unsafe { std::os::unix::net::UnixStream::from_raw_fd(raw_fd) }; + stream + .write(&secret) + .map_err(|e| Error::new(&e.to_string()))?; + + Ok(HashMap::new()) +} + +#[async_std::main] +async fn main() -> Result<(), zbus::Error> { + tracing_subscriber::fmt::init(); + + let backend = Secret; + let cnx = zbus::ConnectionBuilder::session()? + // .name(NAME)? + .serve_at(PATH, backend)? + .build() + .await?; + // NOTE For debugging. + let flags = zbus::fdo::RequestNameFlags::ReplaceExisting + | zbus::fdo::RequestNameFlags::AllowReplacement; + cnx.request_name_with_flags(NAME, flags).await?; + + loop { + pending::<()>().await; + } +} + +struct Request { + handle_path: zvariant::OwnedObjectPath, + sender: Arc>>>, +} + +#[dbus_interface(name = "org.freedesktop.impl.portal.Request")] +impl Request { + async fn close( + &self, + #[zbus(object_server)] server: &zbus::ObjectServer, + ) -> zbus::fdo::Result<()> { + tracing::debug!("Object {} closed", self.handle_path); + server + .remove::(&self.handle_path) + .await?; + + if let Ok(mut guard) = self.sender.lock() { + if let Some(sender) = (*guard).take() { + // This will Err out if the receiver has been dropped. + let _ = sender.send(()); + } + } + + Ok(()) + } +} + +impl Request { + async fn serve( + object_server: &zbus::ObjectServer, + handle_path: OwnedObjectPath, + sender: futures_channel::oneshot::Sender<()>, + ) -> zbus::fdo::Result<()> { + tracing::debug!("Handling object {:?}", handle_path.as_str()); + + let iface = Request { + handle_path: handle_path.clone(), + sender: Arc::new(Mutex::new(Some(sender))), + }; + + object_server.at(handle_path, iface).await?; + + Ok(()) + } +}