Skip to content

Commit

Permalink
Merge pull request #151 from bilelmoussaoui/bilelmoussaoui/secret
Browse files Browse the repository at this point in the history
client: Add a generic Secret type
  • Loading branch information
bilelmoussaoui authored Dec 4, 2024
2 parents 768f653 + 3fbbf46 commit 1bba4ad
Show file tree
Hide file tree
Showing 30 changed files with 274 additions and 228 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions cargo-credential/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl SecretServiceCredential {
let token = cargo_credential::read_token(options, registry)?.expose();

if let Some(item) = items.first() {
item.set_secret(token, "text/utf8")
item.set_secret(token)
.await
.map_err(|err| Error::Other(Box::new(err)))?;
} else {
Expand All @@ -60,7 +60,6 @@ impl SecretServiceCredential {
&attributes,
token,
true,
"text/utf8",
None,
)
.await
Expand Down
16 changes: 8 additions & 8 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl Commands {
};

collection
.create_item(&label, &attributes, &secret, true, "text/plain", None)
.create_item(&label, &attributes, secret, true, None)
.await?;
}
Commands::Lock => collection.lock(None).await?,
Expand Down Expand Up @@ -245,22 +245,22 @@ async fn print_item(
as_hex: bool,
) -> Result<(), Error> {
use std::fmt::Write;
let secret = item.secret().await?;
let bytes = secret.as_bytes();
if secret_only {
let bytes = item.secret().await?;
let mut stdout = std::io::stdout().lock();
if as_hex {
let hex = hex::encode(&bytes);
let hex = hex::encode(bytes);
stdout.write_all(hex.as_bytes())?;
} else {
stdout.write_all(&bytes)?;
stdout.write_all(bytes)?;
}
// Add a new line if we are writing to a tty
if stdout.is_terminal() {
stdout.write_all(b"\n")?;
}
} else {
let label = item.label().await?;
let bytes = item.secret().await?;
let mut attributes = item.attributes().await?;
let created = item.created().await?;
let modified = item.modified().await?;
Expand All @@ -277,15 +277,15 @@ async fn print_item(

// we still fallback to hex if it is not a string
if as_hex {
let hex = hex::encode(&bytes);
let hex = hex::encode(bytes);
writeln!(&mut result, "secret = {hex}").unwrap();
} else {
match std::str::from_utf8(&bytes) {
match std::str::from_utf8(bytes) {
Ok(secret) => {
writeln!(&mut result, "secret = {secret}").unwrap();
}
Err(_) => {
let hex = hex::encode(&bytes);
let hex = hex::encode(bytes);
writeln!(&mut result, "secret = {hex}").unwrap();
}
}
Expand Down
1 change: 1 addition & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ digest = { version = "0.10", optional = true }
endi.workspace = true
futures-lite = { workspace = true, optional = true }
futures-util.workspace = true
getrandom = "0.2"
hkdf = { version = "0.12", optional = true }
hmac = { version = "0.12", optional = true }
md-5 = { version = "0.10", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion client/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ async fn main() -> oo7::Result<()> {
let keyring = Keyring::new().await?;
let attributes = HashMap::from([("attr", "value")]);
keyring
.create_item("Some Label", &attributes, b"secret", true)
.create_item("Some Label", &attributes, "secret", true)
.await?;

let items = keyring.search_items(&attributes).await?;
Expand Down
2 changes: 1 addition & 1 deletion client/examples/basic_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async fn main() -> oo7::Result<()> {
KEYRING
.get()
.unwrap()
.create_item("Some Label", &attributes, b"secret", true)
.create_item("Some Label", &attributes, "secret", true)
.await?;

let items = KEYRING.get().unwrap().search_items(&attributes).await?;
Expand Down
4 changes: 2 additions & 2 deletions client/src/dbus/api/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use futures_util::{Stream, StreamExt};
use serde::Serialize;
use zbus::zvariant::{ObjectPath, OwnedObjectPath, Type};

use super::{Item, Prompt, Properties, Secret, Unlockable, DESTINATION};
use super::{DBusSecret, Item, Prompt, Properties, Unlockable, DESTINATION};
use crate::{
dbus::{Error, ServiceError},
AsAttributes,
Expand Down Expand Up @@ -165,7 +165,7 @@ impl<'a> Collection<'a> {
&self,
label: &str,
attributes: &impl AsAttributes,
secret: &Secret<'_>,
secret: &DBusSecret<'_>,
replace: bool,
window_id: Option<WindowIdentifier>,
) -> Result<Item<'a>, Error> {
Expand Down
10 changes: 5 additions & 5 deletions client/src/dbus/api/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ashpd::WindowIdentifier;
use serde::Serialize;
use zbus::zvariant::{ObjectPath, OwnedObjectPath, Type};

use super::{secret::SecretInner, Prompt, Secret, Session, Unlockable, DESTINATION};
use super::{DBusSecret, Prompt, Session, Unlockable, DESTINATION};
use crate::{
dbus::{Error, ServiceError},
AsAttributes,
Expand Down Expand Up @@ -121,19 +121,19 @@ impl<'a> Item<'a> {
}

#[doc(alias = "GetSecret")]
pub async fn secret(&self, session: &Session<'_>) -> Result<Secret<'_>, Error> {
pub async fn secret(&self, session: &Session<'_>) -> Result<DBusSecret<'_>, Error> {
let inner = self
.inner()
.call_method("GetSecret", &(session))
.await
.map_err::<ServiceError, _>(From::from)?
.body()
.deserialize::<SecretInner>()?;
Secret::from_inner(self.inner().connection(), inner).await
.deserialize::<super::secret::DBusSecretInner>()?;
DBusSecret::from_inner(self.inner().connection(), inner).await
}

#[doc(alias = "SetSecret")]
pub async fn set_secret(&self, secret: &Secret<'_>) -> Result<(), Error> {
pub async fn set_secret(&self, secret: &DBusSecret<'_>) -> Result<(), Error> {
self.inner()
.call_method("SetSecret", &(secret,))
.await
Expand Down
4 changes: 2 additions & 2 deletions client/src/dbus/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ pub(crate) use properties::Properties;
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub use properties::Properties;
pub use secret::Secret;
pub use secret::DBusSecret;
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub use secret::SecretInner;
pub use secret::DBusSecretInner;
pub use service::Service;
pub use session::Session;
62 changes: 40 additions & 22 deletions client/src/dbus/api/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ use zbus::zvariant::{OwnedObjectPath, Type};
use zeroize::{Zeroize, ZeroizeOnDrop};

use super::Session;
use crate::{crypto, dbus::Error, Key};
use crate::{
crypto,
dbus::Error,
secret::{BLOB_CONTENT_TYPE, TEXT_CONTENT_TYPE},
Key, Secret,
};

#[derive(Debug, Serialize, Deserialize, Type)]
#[zvariant(signature = "(oayays)")]
pub struct SecretInner(pub OwnedObjectPath, pub Vec<u8>, pub Vec<u8>, pub String);
/// Same as [`DBusSecret`] without tying the session path to a [`Session`] type.
pub struct DBusSecretInner(pub OwnedObjectPath, pub Vec<u8>, pub Vec<u8>, pub String);

#[derive(Debug, Type, Zeroize, ZeroizeOnDrop)]
#[zvariant(signature = "(oayays)")]
pub struct Secret<'a> {
pub struct DBusSecret<'a> {
#[zeroize(skip)]
pub(crate) session: Arc<Session<'a>>,
pub(crate) parameters: Vec<u8>,
Expand All @@ -22,47 +28,59 @@ pub struct Secret<'a> {
pub(crate) content_type: String,
}

impl<'a> Secret<'a> {
pub(crate) fn new(
session: Arc<Session<'a>>,
secret: impl AsRef<[u8]>,
content_type: &str,
) -> Self {
impl<'a> DBusSecret<'a> {
pub(crate) fn new(session: Arc<Session<'a>>, secret: impl Into<Secret>) -> Self {
let secret = secret.into();
Self {
session,
parameters: vec![],
value: secret.as_ref().to_vec(),
content_type: content_type.to_owned(),
value: secret.as_bytes().to_vec(),
content_type: secret.content_type().to_owned(),
}
}

pub(crate) fn new_encrypted(
session: Arc<Session<'a>>,
secret: impl AsRef<[u8]>,
content_type: &str,
secret: impl Into<Secret>,
aes_key: &Key,
) -> Self {
let iv = crypto::generate_iv();
let secret = crypto::encrypt(secret.as_ref(), aes_key, &iv);
let secret = secret.into();
Self {
session,
value: crypto::encrypt(secret.as_bytes(), aes_key, &iv),
parameters: iv,
value: secret,
content_type: content_type.to_owned(),
content_type: secret.content_type().to_owned(),
}
}

pub(crate) async fn from_inner(
cnx: &zbus::Connection,
inner: SecretInner,
) -> Result<Secret<'_>, Error> {
let secret = Secret {
inner: DBusSecretInner,
) -> Result<Self, Error> {
Ok(Self {
session: Arc::new(Session::new(cnx, inner.0).await?),
parameters: inner.1,
value: inner.2,
content_type: inner.3,
})
}

pub(crate) fn decrypt(&self, key: Option<&Arc<Key>>) -> Result<Secret, Error> {
let value = match key {
Some(key) => &crypto::decrypt(&self.value, key, &self.parameters),
None => &self.value,
};
Ok(secret)

match self.content_type.as_str() {
TEXT_CONTENT_TYPE => Ok(Secret::Text(String::from_utf8(value.to_vec())?)),
BLOB_CONTENT_TYPE => Ok(Secret::blob(value)),
e => {
#[cfg(feature = "tracing")]
tracing::warn!("Unsupported content-type {e}, falling back to blob");
Ok(Secret::blob(value))
}
}
}

/// Session used to encode the secret
Expand All @@ -86,7 +104,7 @@ impl<'a> Secret<'a> {
}
}

impl Serialize for Secret<'_> {
impl Serialize for DBusSecret<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
Expand All @@ -106,6 +124,6 @@ mod tests {

#[test]
fn signature() {
assert_eq!(Secret::SIGNATURE, "(oayays)");
assert_eq!(DBusSecret::SIGNATURE, "(oayays)");
}
}
9 changes: 4 additions & 5 deletions client/src/dbus/api/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use futures_util::{Stream, StreamExt};
use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value};

use super::{
secret::SecretInner, Collection, Item, Prompt, Properties, Secret, Session, Unlockable,
DESTINATION, PATH,
Collection, DBusSecret, Item, Prompt, Properties, Session, Unlockable, DESTINATION, PATH,
};
use crate::{
dbus::{Algorithm, Error, ServiceError},
Expand Down Expand Up @@ -211,14 +210,14 @@ impl<'a> Service<'a> {
&self,
items: &[Item<'_>],
session: &Session<'_>,
) -> Result<HashMap<Item<'_>, Secret<'_>>, Error> {
) -> Result<HashMap<Item<'_>, DBusSecret<'_>>, Error> {
let secrets = self
.inner()
.call_method("GetSecrets", &(items, session))
.await
.map_err::<ServiceError, _>(From::from)?
.body()
.deserialize::<HashMap<OwnedObjectPath, SecretInner>>()?;
.deserialize::<HashMap<OwnedObjectPath, super::secret::DBusSecretInner>>()?;

let cnx = self.inner().connection();
// Item's Hash implementation doesn't make use of any mutable internals
Expand All @@ -227,7 +226,7 @@ impl<'a> Service<'a> {
for (path, secret_inner) in secrets {
output.insert(
Item::new(cnx, path).await?,
Secret::from_inner(cnx, secret_inner).await?,
DBusSecret::from_inner(cnx, secret_inner).await?,
);
}

Expand Down
20 changes: 7 additions & 13 deletions client/src/dbus/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tokio::sync::RwLock;
use zbus::zvariant::{ObjectPath, OwnedObjectPath};

use super::{api, Algorithm, Error, Item};
use crate::{AsAttributes, Key};
use crate::{AsAttributes, Key, Secret};

/// A collection allows to store and retrieve items.
///
Expand Down Expand Up @@ -151,28 +151,22 @@ impl<'a> Collection<'a> {
/// * `secret` - The secret to store.
/// * `replace` - Whether to replace the value if the `attributes` matches
/// an existing `secret`.
/// * `content_type` - The content type of the secret, usually something
/// like `text/plain`.
pub async fn create_item(
&self,
label: &str,
attributes: &impl AsAttributes,
secret: impl AsRef<[u8]>,
secret: impl Into<Secret>,
replace: bool,
content_type: &str,
window_id: Option<WindowIdentifier>,
) -> Result<Item<'a>, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
let secret = match self.algorithm {
Algorithm::Plain => {
api::Secret::new(Arc::clone(&self.session), secret, content_type)
}
Algorithm::Encrypted => api::Secret::new_encrypted(
Algorithm::Plain => api::DBusSecret::new(Arc::clone(&self.session), secret),
Algorithm::Encrypted => api::DBusSecret::new_encrypted(
Arc::clone(&self.session),
secret,
content_type,
self.aes_key.as_ref().unwrap(),
),
};
Expand Down Expand Up @@ -275,18 +269,18 @@ mod tests {
"plain-type-test"
};
attributes.insert("type", value);
let secret = "a password".as_bytes();
let secret = crate::Secret::text("a password");

let collection = service.default_collection().await.unwrap();
let n_items = collection.items().await.unwrap().len();
let n_search_items = collection.search_items(&attributes).await.unwrap().len();

let item = collection
.create_item("A secret", &attributes, secret, true, "text/plain", None)
.create_item("A secret", &attributes, secret.clone(), true, None)
.await
.unwrap();

assert_eq!(*item.secret().await.unwrap(), secret);
assert_eq!(item.secret().await.unwrap(), secret);
assert_eq!(item.attributes().await.unwrap()["type"], value);

assert_eq!(collection.items().await.unwrap().len(), n_items + 1);
Expand Down
Loading

0 comments on commit 1bba4ad

Please sign in to comment.