use std::{
collections::HashMap,
io::Read,
os::{
fd::{AsFd, AsRawFd},
unix::net::UnixStream,
},
};
use futures_util::StreamExt;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use zbus::zvariant::{Fd, ObjectPath, OwnedValue, SerializeDict, Type};
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::Error;
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
pub struct Secret {
secret: Vec<u8>,
}
impl From<Vec<u8>> for Secret {
fn from(secret: Vec<u8>) -> Self {
Self { secret }
}
}
impl std::ops::Deref for Secret {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.secret
}
}
#[derive(SerializeDict, Type, Debug)]
#[zvariant(signature = "dict")]
struct RetrieveOptions {
handle_token: String,
}
impl Default for RetrieveOptions {
fn default() -> Self {
let mut rng = thread_rng();
let token: String = (&mut rng)
.sample_iter(Alphanumeric)
.take(10)
.map(char::from)
.collect();
Self {
handle_token: format!("oo7_{token}"),
}
}
}
#[derive(Debug)]
pub struct SecretProxy<'a>(zbus::Proxy<'a>);
impl<'a> SecretProxy<'a> {
pub async fn new(connection: &zbus::Connection) -> Result<SecretProxy<'a>, zbus::Error> {
let proxy = zbus::ProxyBuilder::new_bare(connection)
.interface("org.freedesktop.portal.Secret")?
.path("/org/freedesktop/portal/desktop")?
.destination("org.freedesktop.portal.Desktop")?
.build()
.await?;
Ok(Self(proxy))
}
#[doc(alias = "RetrieveSecret")]
pub async fn retrieve_secret(&self, fd: &impl AsFd) -> Result<(), Error> {
let options = RetrieveOptions::default();
let cnx = self.0.connection();
let unique_name = cnx.unique_name().unwrap();
let unique_identifier = unique_name.trim_start_matches(':').replace('.', "_");
let path = ObjectPath::try_from(format!(
"/org/freedesktop/portal/desktop/request/{unique_identifier}/{}",
options.handle_token
))
.unwrap();
#[cfg(feature = "tracing")]
tracing::debug!(
"Creating a '{}' proxy and listening for a response",
path.as_str()
);
let request_proxy: zbus::Proxy = zbus::ProxyBuilder::new_bare(cnx)
.interface("org.freedesktop.portal.Request")?
.destination("org.freedesktop.portal.Desktop")?
.path(path)?
.build()
.await?;
let mut signal_stream = request_proxy.receive_signal("Response").await?;
futures_util::try_join!(
async {
let message = signal_stream.next().await.unwrap();
let (response, _details) = message.body::<(u32, HashMap<String, OwnedValue>)>()?;
if response == 0 {
Ok(())
} else {
Err(Error::CancelledPortalRequest)
}
},
async {
match self
.0
.call_method(
"RetrieveSecret",
&(Fd::from(fd.as_fd().as_raw_fd()), &options),
)
.await
{
Ok(_) => Ok(()),
Err(zbus::Error::MethodError(_, _, _)) => Err(Error::PortalNotAvailable),
Err(e) => Err(e.into()),
}?;
Ok(())
},
)?;
Ok(())
}
}
pub async fn retrieve() -> Result<Secret, Error> {
let connection = zbus::Connection::session().await?;
#[cfg(feature = "tracing")]
tracing::debug!("Retrieve service key using org.freedesktop.portal.Secrets");
let proxy = match SecretProxy::new(&connection).await {
Ok(proxy) => Ok(proxy),
Err(zbus::Error::InterfaceNotFound) => Err(Error::PortalNotAvailable),
Err(e) => Err(e.into()),
}?;
let (mut x1, x2) = UnixStream::pair()?;
proxy.retrieve_secret(&x2).await?;
drop(x2);
let mut buf = Vec::new();
x1.read_to_end(&mut buf)?;
#[cfg(feature = "tracing")]
tracing::debug!("Secret received from the portal successfully");
Ok(Secret { secret: buf })
}