Repository: bilelmoussaoui/oo7 (language: rust)

Issue:
cli: Do not assume secrets are valid utf8
Portal secrets are random bytes, hence the cli will crash.

We should load them into a OsString and have a --hex flag to display them as hex.


Discussion / hints:
OsString sounds wrong. On Windows, for example, an OSString has to be valid utf16, right? Binary data is just binary data.
But this is a linux app which could be piped into something else
![image](https://github.com/bilelmoussaoui/oo7/assets/7660997/763f56b1-e326-4eec-9e91-01908c621959)
i agree, this is not great (both cases)

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index b989bdce2..e1571d78f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,3 +16,16 @@ homepage = "https://github.com/bilelmoussaoui/oo7"
 license = "MIT"
 rust-version = "1.66"
 exclude = ["org.freedesktop.Secrets.xml"]
+
+[workspace.dependencies]
+futures-channel = "0.3"
+futures-lite = "2.1"
+futures-util = "0.3"
+oo7 = { path = "client"}
+serde = { version = "1.0", features = ["derive"] }
+tokio = { version = "1.17", default-features = false }
+tracing = "0.1"
+tracing-subscriber = "0.3"
+zbus = { version = "4.0", default-features = false }
+zeroize = { version = "1", features = ["zeroize_derive"] }
+zvariant = { version = "4.0", default-features = false, features = ["gvariant"]}
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 4e4a34a62..403b8d843 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -10,8 +10,8 @@ rust-version.workspace = true
 version.workspace = true
 
 [dependencies]
-tokio = { version = "1.17", features = [ "macros", "rt-multi-thread" ] }
 chrono = { version = "0.4.31", default-features = false, features = ["alloc", "clock"] }
 clap = { version = "4.4.6", features = [ "cargo", "derive" ] }
-oo7 = { path = "../client" }
+oo7.workspace = true
 rpassword = "7.2.0"
+tokio = { workspace = true, features = [ "macros", "rt-multi-thread" ] }
diff --git a/cli/src/main.rs b/cli/src/main.rs
index a28a3e65f..0f0a72f3d 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -152,10 +152,7 @@ async fn lookup(attributes: &impl AsAttributes) -> Result<(), Error> {
     let items = collection.search_items(attributes).await?;
 
     if let Some(item) = items.first() {
-        let bytes = item.secret().await?;
-        let secret =
-            std::str::from_utf8(&bytes).map_err(|_| Error::new("Secret is not valid utf-8"))?;
-        println!("{secret}");
+        print_item(item).await?;
     }
 
     Ok(())
@@ -206,9 +203,6 @@ async fn print_item<'a>(item: &oo7::dbus::Item<'a>) -> Result<(), Error> {
 
     let label = item.label().await?;
     let bytes = item.secret().await?;
-    // TODO Maybe show bytes in hex instead of failing?
-    let secret =
-        std::str::from_utf8(&bytes).map_err(|_| Error::new("Secret is not valid utf-8"))?;
     let mut attributes = item.attributes().await?;
     let created = item.created().await?;
     let modified = item.modified().await?;
@@ -221,7 +215,15 @@ async fn print_item<'a>(item: &oo7::dbus::Item<'a>) -> Result<(), Error> {
         .with_timezone(&chrono::Local);
 
     let mut result = format!("[{label}]\n");
-    writeln!(&mut result, "secret = {secret}").unwrap();
+    match std::str::from_utf8(&bytes) {
+        Ok(secret) => {
+            writeln!(&mut result, "secret = {secret}").unwrap();
+        }
+        Err(_) => {
+            writeln!(&mut result, "secret = {:02X?}", bytes.as_slice()).unwrap();
+        }
+    }
+
     writeln!(
         &mut result,
         "created = {}",
diff --git a/client/Cargo.toml b/client/Cargo.toml
index ea8133693..24d0f81e4 100644
--- a/client/Cargo.toml
+++ b/client/Cargo.toml
@@ -13,40 +13,40 @@ rust-version.workspace = true
 version.workspace = true
 
 [dependencies]
-zbus = { version = "3.5.0", default-features = false, features = ["gvariant"] }
-serde = "1.0"
-futures-lite = { version = "2.1", optional = true }
-futures-util = "0.3"
-rand = { version = "0.8", default-features = false }
-tokio = { version = "1.17", features = [
-    "sync",
-    "fs",
-    "io-util",
-], optional = true, default-features = false }
-byteorder = "1.0"
-num = "0.4.0"
-num-bigint-dig = { version = "0.8", features = ["zeroize"] }
-tracing = { version = "0.1", optional = true }
-zeroize = { version = "1", features = ["zeroize_derive"] }
 aes = { version = "0.8", features = ["zeroize"], optional = true }
+async-fs = { version = "2.1.0", optional = true }
+async-io = { version = "2.2.2", optional = true }
+async-lock = { version = "3.2.0", optional = true }
+blocking = { version = "1.5.1", optional = true }
 cbc = { version = "0.1", features = ["zeroize"], optional = true }
 cipher = { version = "0.4", features = [
     "rand_core",
     "zeroize",
 ], optional = true }
 digest = { version = "0.10", optional = true }
+futures-lite = { workspace = true, optional = true }
+futures-util.workspace = true
 hkdf = { version = "0.12", optional = true }
 hmac = { version = "0.12", optional = true }
+num = "0.4.0"
+num-bigint-dig = { version = "0.8", features = ["zeroize"] }
+openssl = { version = "0.10", optional = true }
 pbkdf2 = { version = "0.12", optional = true }
+rand = { version = "0.8", default-features = false }
+serde.workspace = true
 sha2 = { version = "0.10", optional = true }
-openssl = { version = "0.10", optional = true }
-async-fs = { version = "2.1.0", optional = true }
-async-lock = { version = "3.2.0", optional = true }
-async-io = { version = "2.2.2", optional = true }
-blocking = { version = "1.5.1", optional = true }
+tokio = { workspace = true, features = [
+    "sync",
+    "fs",
+    "io-util",
+], optional = true, default-features = false }
+tracing = { workspace = true, optional = true }
+zbus.workspace = true
+zvariant.workspace = true
+zeroize.workspace = true
 
 [dev-dependencies]
-tokio = { version = "1.11", features = ["macros", "rt-multi-thread"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 [features]
 default = ["local_tests", "async-std", "native_crypto"]
diff --git a/client/src/dbus/api/collection.rs b/client/src/dbus/api/collection.rs
index 4e1f714b5..8143311f9 100644
--- a/client/src/dbus/api/collection.rs
+++ b/client/src/dbus/api/collection.rs
@@ -19,9 +19,9 @@ use crate::{
 pub struct Collection<'a>(zbus::Proxy<'a>);
 
 impl<'a> ProxyDefault for Collection<'a> {
-    const INTERFACE: &'static str = "org.freedesktop.Secret.Collection";
-    const DESTINATION: &'static str = DESTINATION;
-    const PATH: &'static str = "/";
+    const INTERFACE: Option<&'static str> = Some("org.freedesktop.Secret.Collection");
+    const DESTINATION: Option<&'static str> = Some(DESTINATION);
+    const PATH: Option<&'static str> = None;
 }
 
 impl<'a> From<zbus::Proxy<'a>> for Collection<'a> {
@@ -71,7 +71,7 @@ impl<'a> Collection<'a> {
         let stream = self.inner().receive_signal("ItemCreated").await?;
         let conn = self.inner().connection();
         Ok(stream.filter_map(move |message| async move {
-            let path = message.body::<OwnedObjectPath>().ok()?;
+            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
             Item::new(conn, path).await.ok()
         }))
     }
@@ -79,7 +79,9 @@ impl<'a> Collection<'a> {
     #[doc(alias = "ItemDeleted")]
     pub async fn receive_item_deleted(&self) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
         let stream = self.inner().receive_signal("ItemDeleted").await?;
-        Ok(stream.filter_map(move |message| async move { message.body::<OwnedObjectPath>().ok() }))
+        Ok(stream.filter_map(move |message| async move {
+            message.body().deserialize::<OwnedObjectPath>().ok()
+        }))
     }
 
     #[doc(alias = "ItemChanged")]
@@ -87,7 +89,7 @@ impl<'a> Collection<'a> {
         let stream = self.inner().receive_signal("ItemChanged").await?;
         let conn = self.inner().connection();
         Ok(stream.filter_map(move |message| async move {
-            let path = message.body::<OwnedObjectPath>().ok()?;
+            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
             Item::new(conn, path).await.ok()
         }))
     }
@@ -136,7 +138,8 @@ impl<'a> Collection<'a> {
             .call_method("Delete", &())
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<OwnedObjectPath>()?;
+            .body()
+            .deserialize::<OwnedObjectPath>()?;
         if let Some(prompt) = Prompt::new(self.inner().connection(), prompt_path).await? {
             let _ = prompt.receive_completed().await?;
         }
@@ -154,7 +157,7 @@ impl<'a> Collection<'a> {
             .await
             .map_err::<ServiceError, _>(From::from)?;
 
-        let item_paths = msg.body::<Vec<OwnedObjectPath>>()?;
+        let item_paths = msg.body().deserialize::<Vec<OwnedObjectPath>>()?;
         Item::from_paths(self.inner().connection(), item_paths).await
     }
 
@@ -172,7 +175,8 @@ impl<'a> Collection<'a> {
             .call_method("CreateItem", &(properties, secret, replace))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<(OwnedObjectPath, OwnedObjectPath)>()?;
+            .body()
+            .deserialize::<(OwnedObjectPath, OwnedObjectPath)>()?;
         let cnx = self.inner().connection();
         let item_path = if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
             let response = prompt.receive_completed().await?;
diff --git a/client/src/dbus/api/item.rs b/client/src/dbus/api/item.rs
index 527827861..85ad65a7b 100644
--- a/client/src/dbus/api/item.rs
+++ b/client/src/dbus/api/item.rs
@@ -18,9 +18,9 @@ use crate::{
 pub struct Item<'a>(zbus::Proxy<'a>);
 
 impl<'a> ProxyDefault for Item<'a> {
-    const INTERFACE: &'static str = "org.freedesktop.Secret.Item";
-    const DESTINATION: &'static str = DESTINATION;
-    const PATH: &'static str = "/";
+    const INTERFACE: Option<&'static str> = Some("org.freedesktop.Secret.Item");
+    const DESTINATION: Option<&'static str> = Some(DESTINATION);
+    const PATH: Option<&'static str> = None;
 }
 
 impl<'a> From<zbus::Proxy<'a>> for Item<'a> {
@@ -112,7 +112,8 @@ impl<'a> Item<'a> {
             .call_method("Delete", &())
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<OwnedObjectPath>()?;
+            .body()
+            .deserialize::<OwnedObjectPath>()?;
         if let Some(prompt) = Prompt::new(self.inner().connection(), prompt_path).await? {
             let _ = prompt.receive_completed().await?;
         }
@@ -126,7 +127,8 @@ impl<'a> Item<'a> {
             .call_method("GetSecret", &(session))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<SecretInner>()?;
+            .body()
+            .deserialize::<SecretInner>()?;
         Secret::from_inner(self.inner().connection(), inner).await
     }
 
diff --git a/client/src/dbus/api/prompt.rs b/client/src/dbus/api/prompt.rs
index 7504bf0b9..938c1d158 100644
--- a/client/src/dbus/api/prompt.rs
+++ b/client/src/dbus/api/prompt.rs
@@ -16,9 +16,9 @@ use crate::dbus::{Error, ServiceError};
 pub struct Prompt<'a>(zbus::Proxy<'a>);
 
 impl<'a> ProxyDefault for Prompt<'a> {
-    const INTERFACE: &'static str = "org.freedesktop.Secret.Prompt";
-    const DESTINATION: &'static str = DESTINATION;
-    const PATH: &'static str = "/";
+    const INTERFACE: Option<&'static str> = Some("org.freedesktop.Secret.Prompt");
+    const DESTINATION: Option<&'static str> = Some(DESTINATION);
+    const PATH: Option<&'static str> = None;
 }
 
 impl<'a> From<zbus::Proxy<'a>> for Prompt<'a> {
@@ -77,7 +77,7 @@ impl<'a> Prompt<'a> {
         let (value, _) = futures_util::try_join!(
             async {
                 let message = stream.next().await.unwrap();
-                let (dismissed, result) = message.body::<(bool, OwnedValue)>()?;
+                let (dismissed, result) = message.body().deserialize::<(bool, OwnedValue)>()?;
                 if dismissed {
                     Err(Error::Dismissed)
                 } else {
diff --git a/client/src/dbus/api/properties.rs b/client/src/dbus/api/properties.rs
index 0dad44329..67ba2098e 100644
--- a/client/src/dbus/api/properties.rs
+++ b/client/src/dbus/api/properties.rs
@@ -89,7 +89,10 @@ impl<'de> Deserialize<'de> for Properties {
         } else {
             let label = zvariant::Str::try_from(map.get(ITEM_PROPERTY_LABEL).unwrap()).unwrap();
             let attributes = HashMap::<String, String>::try_from(
-                map.get(ITEM_PROPERTY_ATTRIBUTES).unwrap().clone(),
+                map.get(ITEM_PROPERTY_ATTRIBUTES)
+                    .unwrap()
+                    .try_clone()
+                    .unwrap(),
             )
             .unwrap();
             Ok(Self::for_item(&label, &attributes))
@@ -99,8 +102,7 @@ impl<'de> Deserialize<'de> for Properties {
 
 #[cfg(test)]
 mod tests {
-    use byteorder::LE;
-    use zbus::zvariant::{self, from_slice, to_bytes, EncodingContext as Context, Type};
+    use zbus::zvariant::{self, serialized::Context, to_bytes, Endian, Type};
 
     use super::*;
 
@@ -108,9 +110,9 @@ mod tests {
     fn serialize_label() {
         let properties = Properties::for_collection("some_label");
 
-        let ctxt = Context::<LE>::new_dbus(0);
+        let ctxt = Context::new_dbus(Endian::Little, 0);
         let encoded = to_bytes(ctxt, &properties).unwrap();
-        let decoded: HashMap<&str, Value<'_>> = from_slice(&encoded, ctxt).unwrap();
+        let decoded: HashMap<&str, Value<'_>> = encoded.deserialize().unwrap().0;
 
         assert_eq!(
             decoded[COLLECTION_PROPERTY_LABEL],
@@ -126,9 +128,9 @@ mod tests {
         attributes.insert("some", "attribute");
         let properties = Properties::for_item("some_label", &attributes);
 
-        let ctxt = Context::<LE>::new_dbus(0);
+        let ctxt = Context::new_dbus(Endian::Little, 0);
         let encoded = to_bytes(ctxt, &properties).unwrap();
-        let decoded: HashMap<&str, Value<'_>> = from_slice(&encoded, ctxt).unwrap();
+        let decoded: HashMap<&str, Value<'_>> = encoded.deserialize().unwrap().0;
 
         assert_eq!(decoded[ITEM_PROPERTY_LABEL], Value::from("some_label"));
         assert!(!decoded.contains_key(COLLECTION_PROPERTY_LABEL));
diff --git a/client/src/dbus/api/service.rs b/client/src/dbus/api/service.rs
index c96dd7ba3..19eb8fb5e 100644
--- a/client/src/dbus/api/service.rs
+++ b/client/src/dbus/api/service.rs
@@ -21,9 +21,9 @@ use crate::{
 pub struct Service<'a>(zbus::Proxy<'a>);
 
 impl<'a> ProxyDefault for Service<'a> {
-    const INTERFACE: &'static str = "org.freedesktop.Secret.Service";
-    const DESTINATION: &'static str = DESTINATION;
-    const PATH: &'static str = PATH;
+    const INTERFACE: Option<&'static str> = Some("org.freedesktop.Secret.Service");
+    const DESTINATION: Option<&'static str> = Some(DESTINATION);
+    const PATH: Option<&'static str> = Some(PATH);
 }
 
 impl<'a> From<zbus::Proxy<'a>> for Service<'a> {
@@ -52,7 +52,7 @@ impl<'a> Service<'a> {
         let stream = self.inner().receive_signal("CollectionCreated").await?;
         let conn = self.inner().connection();
         Ok(stream.filter_map(move |message| async move {
-            let path = message.body::<OwnedObjectPath>().ok()?;
+            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
             Collection::new(conn, path).await.ok()
         }))
     }
@@ -64,7 +64,7 @@ impl<'a> Service<'a> {
         let stream = self.inner().receive_signal("CollectionDeleted").await?;
         let conn = self.inner().connection();
         Ok(stream.filter_map(move |message| async move {
-            let path = message.body::<OwnedObjectPath>().ok()?;
+            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
             Collection::new(conn, path).await.ok()
         }))
     }
@@ -76,7 +76,7 @@ impl<'a> Service<'a> {
         let stream = self.inner().receive_signal("CollectionChanged").await?;
         let conn = self.inner().connection();
         Ok(stream.filter_map(move |message| async move {
-            let path = message.body::<OwnedObjectPath>().ok()?;
+            let path = message.body().deserialize::<OwnedObjectPath>().ok()?;
             Collection::new(conn, path).await.ok()
         }))
     }
@@ -103,7 +103,8 @@ impl<'a> Service<'a> {
             .call_method("OpenSession", &(&algorithm, key))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<(OwnedValue, OwnedObjectPath)>()?;
+            .body()
+            .deserialize::<(OwnedValue, OwnedObjectPath)>()?;
         let session = Session::new(self.inner().connection(), session_path).await?;
 
         let key = match algorithm {
@@ -126,7 +127,8 @@ impl<'a> Service<'a> {
             .call_method("CreateCollection", &(properties, alias.unwrap_or_default()))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<(OwnedObjectPath, OwnedObjectPath)>()?;
+            .body()
+            .deserialize::<(OwnedObjectPath, OwnedObjectPath)>()?;
 
         let collection_path = if let Some(prompt) =
             Prompt::new(self.inner().connection(), prompt_path).await?
@@ -149,7 +151,8 @@ impl<'a> Service<'a> {
             .call_method("SearchItems", &(attributes.as_attributes()))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<(Vec<OwnedObjectPath>, Vec<OwnedObjectPath>)>()?;
+            .body()
+            .deserialize::<(Vec<OwnedObjectPath>, Vec<OwnedObjectPath>)>()?;
         let cnx = self.inner().connection();
 
         let unlocked_items = Item::from_paths(cnx, unlocked_item_paths).await?;
@@ -164,7 +167,8 @@ impl<'a> Service<'a> {
             .call_method("Unlock", &(items))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
+            .body()
+            .deserialize::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
         let cnx = self.inner().connection();
 
         if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
@@ -182,7 +186,8 @@ impl<'a> Service<'a> {
             .call_method("Lock", &(items))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
+            .body()
+            .deserialize::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
         let cnx = self.inner().connection();
 
         if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
@@ -206,7 +211,8 @@ impl<'a> Service<'a> {
             .call_method("GetSecrets", &(items, session))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<HashMap<OwnedObjectPath, SecretInner>>()?;
+            .body()
+            .deserialize::<HashMap<OwnedObjectPath, SecretInner>>()?;
 
         let cnx = self.inner().connection();
         let mut output = HashMap::with_capacity(secrets.capacity());
@@ -227,7 +233,8 @@ impl<'a> Service<'a> {
             .call_method("ReadAlias", &(name))
             .await
             .map_err::<ServiceError, _>(From::from)?
-            .body::<OwnedObjectPath>()?;
+            .body()
+            .deserialize::<OwnedObjectPath>()?;
 
         if collection_path != OwnedObjectPath::default() {
             let collection = Collection::new(self.inner().connection(), collection_path).await?;
diff --git a/client/src/dbus/api/session.rs b/client/src/dbus/api/session.rs
index 29b802420..9cc79dac0 100644
--- a/client/src/dbus/api/session.rs
+++ b/client/src/dbus/api/session.rs
@@ -15,9 +15,9 @@ use crate::dbus::{Error, ServiceError};
 pub struct Session<'a>(zbus::Proxy<'a>);
 
 impl<'a> ProxyDefault for Session<'a> {
-    const INTERFACE: &'static str = "org.freedesktop.Secret.Session";
-    const DESTINATION: &'static str = DESTINATION;
-    const PATH: &'static str = "/";
+    const INTERFACE: Option<&'static str> = Some("org.freedesktop.Secret.Session");
+    const DESTINATION: Option<&'static str> = Some(DESTINATION);
+    const PATH: Option<&'static str> = None;
 }
 
 impl<'a> From<zbus::Proxy<'a>> for Session<'a> {
diff --git a/client/src/dbus/error.rs b/client/src/dbus/error.rs
index 3ab8ed31b..d0f6b04ed 100644
--- a/client/src/dbus/error.rs
+++ b/client/src/dbus/error.rs
@@ -3,9 +3,9 @@ use std::fmt;
 /// DBus Secret Service specific errors.
 /// <https://specifications.freedesktop.org/secret-service/latest/ch15.html>
 #[derive(zbus::DBusError, Debug)]
-#[dbus_error(prefix = "org.freedesktop.Secret.Error")]
+#[zbus(prefix = "org.freedesktop.Secret.Error")]
 pub enum ServiceError {
-    #[dbus_error(zbus_error)]
+    #[zbus(error)]
     /// ZBus specific error.
     ZBus(zbus::Error),
     /// Collection/Item is locked.
diff --git a/client/src/key.rs b/client/src/key.rs
index 026bf59f4..f70ea32fc 100644
--- a/client/src/key.rs
+++ b/client/src/key.rs
@@ -67,8 +67,8 @@ impl From<&Key> for zvariant::Value<'_> {
 impl From<zvariant::OwnedValue> for Key {
     fn from(value: zvariant::OwnedValue) -> Self {
         let mut key = zeroize::Zeroizing::new(vec![]);
-        for value in value.downcast_ref::<zvariant::Array>().unwrap().get() {
-            key.push(*value.downcast_ref::<u8>().unwrap());
+        for value in value.downcast_ref::<zvariant::Array>().unwrap().inner() {
+            key.push(value.downcast_ref::<u8>().unwrap());
         }
         Key::new(key.to_vec())
     }
diff --git a/client/src/portal/api/mod.rs b/client/src/portal/api/mod.rs
index c9d761a78..bdf00129a 100644
--- a/client/src/portal/api/mod.rs
+++ b/client/src/portal/api/mod.rs
@@ -22,7 +22,7 @@ use rand::Rng;
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "tokio")]
 use tokio::{fs, io, io::AsyncWriteExt};
-use zbus::zvariant::{self, Type};
+use zbus::zvariant::{self, serialized::Context, Endian, Type};
 
 /// Used for newly created [`Keyring`]s
 const DEFAULT_ITERATION_COUNT: u32 = 100000;
@@ -52,9 +52,9 @@ use crate::{
     AsAttributes, Key,
 };
 
-pub(super) fn gvariant_encoding() -> &'static zvariant::EncodingContext<byteorder::LE> {
-    static ENCODING: OnceLock<zvariant::EncodingContext<byteorder::LE>> = OnceLock::new();
-    ENCODING.get_or_init(|| zvariant::EncodingContext::<byteorder::LE>::new_gvariant(0))
+pub(super) fn gvariant_encoding() -> &'static Context {
+    static ENCODING: OnceLock<Context> = OnceLock::new();
+    ENCODING.get_or_init(|| Context::new_gvariant(Endian::Little, 0))
 }
 
 /// Logical contents of a keyring file
@@ -219,7 +219,7 @@ impl Keyring {
 
         blob.push(MAJOR_VERSION);
         blob.push(MINOR_VERSION);
-        blob.append(&mut zvariant::to_bytes(*gvariant_encoding(), &self)?);
+        blob.append(&mut zvariant::to_bytes(*gvariant_encoding(), &self)?.to_vec());
 
         Ok(blob)
     }
@@ -261,7 +261,9 @@ impl TryFrom<&[u8]> for Keyring {
         }
 
         if let Some(data) = value.get((FILE_HEADER_LEN + 2)..) {
-            let keyring: Self = zvariant::from_slice(data, *gvariant_encoding())?;
+            let keyring: Self = zvariant::serialized::Data::new(data, *gvariant_encoding())
+                .deserialize()?
+                .0;
 
             if keyring.salt.len() != keyring.salt_size as usize {
                 Err(Error::SaltSizeMismatch(
diff --git a/client/src/portal/item.rs b/client/src/portal/item.rs
index c78a496aa..39aa34919 100644
--- a/client/src/portal/item.rs
+++ b/client/src/portal/item.rs
@@ -105,7 +105,7 @@ impl Item {
     pub(crate) fn encrypt(&self, key: &Key) -> Result<EncryptedItem, Error> {
         key.check_strength()?;
 
-        let decrypted = Zeroizing::new(zvariant::to_bytes(*gvariant_encoding(), &self)?);
+        let decrypted = Zeroizing::new(zvariant::to_bytes(*gvariant_encoding(), &self)?.to_vec());
 
         let iv = crypto::generate_iv();
 
@@ -131,6 +131,8 @@ impl TryFrom<&[u8]> for Item {
     type Error = Error;
 
     fn try_from(value: &[u8]) -> Result<Self, Error> {
-        Ok(zvariant::from_slice(value, *gvariant_encoding())?)
+        Ok(zvariant::serialized::Data::new(value, *gvariant_encoding())
+            .deserialize()?
+            .0)
     }
 }
diff --git a/client/src/portal/secret.rs b/client/src/portal/secret.rs
index a1ef8ed56..804bb28a9 100644
--- a/client/src/portal/secret.rs
+++ b/client/src/portal/secret.rs
@@ -4,10 +4,7 @@
 use std::{
     collections::HashMap,
     io::Read,
-    os::{
-        fd::{AsFd, AsRawFd},
-        unix::net::UnixStream,
-    },
+    os::{fd::AsFd, unix::net::UnixStream},
 };
 
 use futures_util::StreamExt;
@@ -70,9 +67,9 @@ impl Default for RetrieveOptions {
 pub struct SecretProxy<'a>(zbus::Proxy<'a>);
 
 impl<'a> ProxyDefault for SecretProxy<'a> {
-    const INTERFACE: &'static str = "org.freedesktop.portal.Secret";
-    const DESTINATION: &'static str = "org.freedesktop.portal.Desktop";
-    const PATH: &'static str = "/org/freedesktop/portal/desktop";
+    const INTERFACE: Option<&'static str> = Some("org.freedesktop.portal.Secret");
+    const DESTINATION: Option<&'static str> = Some("org.freedesktop.portal.Desktop");
+    const PATH: Option<&'static str> = Some("/org/freedesktop/portal/desktop");
 }
 
 impl<'a> From<zbus::Proxy<'a>> for SecretProxy<'a> {
@@ -113,7 +110,7 @@ impl<'a> SecretProxy<'a> {
             "Creating a '{}' proxy and listening for a response",
             path.as_str()
         );
-        let request_proxy: zbus::Proxy = zbus::ProxyBuilder::new_bare(cnx)
+        let request_proxy: zbus::Proxy = zbus::ProxyBuilder::new(cnx)
             .interface("org.freedesktop.portal.Request")?
             .destination("org.freedesktop.portal.Desktop")?
             .path(path)?
@@ -125,7 +122,9 @@ impl<'a> SecretProxy<'a> {
         futures_util::try_join!(
             async {
                 let message = signal_stream.next().await.unwrap();
-                let (response, _details) = message.body::<(u32, HashMap<String, OwnedValue>)>()?;
+                let (response, _details) = message
+                    .body()
+                    .deserialize::<(u32, HashMap<String, OwnedValue>)>()?;
                 if response == 0 {
                     Ok(())
                 } else {
@@ -135,10 +134,7 @@ impl<'a> SecretProxy<'a> {
             async {
                 match self
                     .0
-                    .call_method(
-                        "RetrieveSecret",
-                        &(Fd::from(fd.as_fd().as_raw_fd()), &options),
-                    )
+                    .call_method("RetrieveSecret", &(Fd::from(fd), &options))
                     .await
                 {
                     Ok(_) => Ok(()),
diff --git a/portal/Cargo.toml b/portal/Cargo.toml
index de5cb485e..2f18268dc 100644
--- a/portal/Cargo.toml
+++ b/portal/Cargo.toml
@@ -10,14 +10,14 @@ rust-version.workspace = true
 version.workspace = true
 
 [dependencies]
-futures-channel = "0.3"
-futures-util = "0.3"
-oo7 = { path = "../client", features = ["unstable"]}
+futures-channel.workspace = true
+futures-util.workspace = true
+oo7 = { workspace = true, features = ["unstable"] }
 ring = "0.17.5"
 secrecy = { version = "0.8", features = ["alloc"] }
-serde = { version = "1.0", features = ["derive"] }
-tokio = { version = "1.17", features = ["io-util", "net", "macros", "rt-multi-thread"] }
+serde.workspace = true
+tokio = { workspace = true, features = ["io-util", "net", "macros", "rt-multi-thread"] }
 tracing = "0.1"
-tracing-subscriber = "0.3"
-zbus = "3.5.0"
-zeroize = { version = "1", features = ["zeroize_derive"] }
+tracing-subscriber.workspace = true
+zbus.workspace = true
+zeroize.workspace = true
diff --git a/portal/src/main.rs b/portal/src/main.rs
index ceb96e918..9966a6345 100644
--- a/portal/src/main.rs
+++ b/portal/src/main.rs
@@ -1,23 +1,19 @@
 mod error;
 mod request;
 
-use std::{
-    collections::HashMap,
-    future::pending,
-    os::{
-        fd::{AsRawFd, FromRawFd},
-        unix::net::UnixStream,
-    },
-};
+use std::{collections::HashMap, future::pending, os::unix::net::UnixStream};
 
 use futures_util::FutureExt;
 use oo7::{
     dbus::Service,
-    zbus::{self, dbus_interface, zvariant, ProxyDefault},
+    zbus::{
+        self,
+        zvariant::{self, OwnedObjectPath},
+        ProxyDefault,
+    },
 };
 use ring::rand::SecureRandom;
 use tokio::io::AsyncWriteExt;
-use zvariant::OwnedObjectPath;
 
 use crate::{
     error::Error,
@@ -30,7 +26,7 @@ const PORTAL_NAME: &str = "org.freedesktop.impl.portal.desktop.oo7";
 
 struct Secret;
 
-#[dbus_interface(name = "org.freedesktop.impl.portal.Secret")]
+#[zbus::interface(name = "org.freedesktop.impl.portal.Secret")]
 impl Secret {
     #[dbus_interface(property, name = "version")]
     fn version(&self) -> u32 {
@@ -43,7 +39,7 @@ impl Secret {
         #[zbus(object_server)] object_server: &zbus::ObjectServer,
         handle: OwnedObjectPath,
         app_id: &str,
-        fd: zvariant::Fd,
+        fd: zvariant::OwnedFd,
         options: HashMap<&str, zvariant::Value<'_>>,
     ) -> Result<(ResponseType, HashMap<&str, zvariant::OwnedValue>), Error> {
         tracing::debug!("Request from app: {app_id} with options: {options:?}");
@@ -93,7 +89,7 @@ fn generate_secret() -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
 }
 
 /// Generates, stores and send the secret back to the fd stream
-async fn send_secret_to_app(app_id: &str, fd: zvariant::Fd) -> Result<(), Error> {
+async fn send_secret_to_app(app_id: &str, fd: zvariant::OwnedFd) -> Result<(), Error> {
     let service = Service::new().await?;
     let collection = match service.default_collection().await {
         Err(oo7::dbus::Error::NotFound(_)) => {
@@ -125,8 +121,8 @@ async fn send_secret_to_app(app_id: &str, fd: zvariant::Fd) -> Result<(), Error>
     };
 
     // Write the secret to the FD.
-    let raw_fd = fd.as_raw_fd();
-    let mut stream = unsafe { tokio::net::UnixStream::from_std(UnixStream::from_raw_fd(raw_fd)) }?;
+    let mut stream =
+        tokio::net::UnixStream::from_std(UnixStream::from(std::os::fd::OwnedFd::from(fd)))?;
     stream.write_all(&secret).await?;
 
     Ok(())
@@ -138,7 +134,7 @@ async fn main() -> Result<(), zbus::Error> {
 
     let backend = Secret;
     let cnx = zbus::ConnectionBuilder::session()?
-        .serve_at(oo7::portal::SecretProxy::PATH, backend)?
+        .serve_at(oo7::portal::SecretProxy::PATH.unwrap(), backend)?
         .build()
         .await?;
     // NOTE For debugging.
diff --git a/portal/src/request.rs b/portal/src/request.rs
index a1a56d703..55f5c9f6d 100644
--- a/portal/src/request.rs
+++ b/portal/src/request.rs
@@ -1,7 +1,7 @@
 use std::sync::Mutex;
 
 use oo7::zbus::{
-    self, dbus_interface,
+    self,
     zvariant::{self, ObjectPath, Type},
 };
 use serde::Serialize;
@@ -31,7 +31,7 @@ impl Request {
     }
 }
 
-#[dbus_interface(name = "org.freedesktop.impl.portal.Request")]
+#[zbus::interface(name = "org.freedesktop.impl.portal.Request")]
 impl Request {
     async fn close(
         &self,


Test patch: