Skip to content

Commit

Permalink
init doc + unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
soywod committed Oct 19, 2024
1 parent 160ad9f commit 6df3d93
Showing 1 changed file with 123 additions and 9 deletions.
132 changes: 123 additions & 9 deletions src/keyutils_persistent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# keyutils-persistent credential store
TODO
This store is a combination of the [keyutils](crate::keyutils) store
backed up with a persistent [secret-service](crate::secret_service)
store.
*/

use log::debug;

use super::credential::{
Expand All @@ -14,29 +17,49 @@ use super::error::{Error, Result};
use super::keyutils::KeyutilsCredential;
use super::secret_service::SsCredential;

/// Representation of a keyutils-persistent credential.
///
/// The credential owns a [KeyutilsCredential] for in-memory usage and
/// a [SsCredential] for persistence.
#[derive(Debug, Clone)]
pub struct KeyutilsPersistentCredential {
keyutils: KeyutilsCredential,
ss: SsCredential,
}

impl CredentialApi for KeyutilsPersistentCredential {
/// Set a password in the underlying store
fn set_password(&self, password: &str) -> Result<()> {
self.set_secret(password.as_bytes())
}

/// Set a secret in the underlying store
///
/// It sets first the secret in keyutils, then in
/// secret-service. If the late one fails, keyutils secret change
/// is reverted.
fn set_secret(&self, secret: &[u8]) -> Result<()> {
let prev_secret = self.keyutils.get_secret()?;
let prev_secret = self.keyutils.get_secret();
self.keyutils.set_secret(secret)?;

if let Err(err) = self.ss.set_secret(secret) {
self.keyutils.set_secret(&prev_secret)?;
match prev_secret {
Ok(ref secret) => self.keyutils.set_secret(secret),
Err(Error::NoEntry) => self.keyutils.delete_credential(),
Err(err) => Err(err),
}?;

return Err(err);
}

Ok(())
}

/// Retrieve a password from the underlying store
///
/// The password is retrieved from keyutils. In case of error, the
/// password is retrieved from secret-service instead (and
/// keyutils is updated).
fn get_password(&self) -> Result<String> {
match self.keyutils.get_password() {
Ok(password) => {
Expand All @@ -53,6 +76,11 @@ impl CredentialApi for KeyutilsPersistentCredential {
Ok(password)
}

/// Retrieve a secret from the underlying store
///
/// The secret is retrieved from keyutils. In case of error, the
/// secret is retrieved from secret-service instead (and keyutils
/// is updated).
fn get_secret(&self) -> Result<Vec<u8>> {
match self.keyutils.get_secret() {
Ok(secret) => {
Expand All @@ -69,6 +97,10 @@ impl CredentialApi for KeyutilsPersistentCredential {
Ok(secret)
}

/// Delete a password from the underlying store.
///
/// The credential is deleted from both keyutils and
/// secret-service.
fn delete_credential(&self) -> Result<()> {
if let Err(err) = self.keyutils.delete_credential() {
debug!("cannot delete keyutils credential: {err}");
Expand All @@ -87,21 +119,26 @@ impl CredentialApi for KeyutilsPersistentCredential {
}

impl KeyutilsPersistentCredential {
/// Create the platform credential for a Keyutils entry.
///
/// An explicit target string is interpreted as the KeyRing to use for the entry.
/// If none is provided, then we concatenate the user and service in the string
/// `keyring-rs:user@service`.
pub fn new_with_target(target: Option<&str>, service: &str, user: &str) -> Result<Self> {
let ss = SsCredential::new_with_target(target, service, user)?;
let keyutils = KeyutilsCredential::new_with_target(target, service, user)?;
Ok(Self { keyutils, ss })
}
}

/// The builder for secret-service-with-keyutils credentials
/// The builder for keyutils-persistent credentials
#[derive(Debug, Default)]
pub struct KeyutilsPersistentCredentialBuilder {}

/// Returns an instance of the secret-service-with-keyutils credential builder.
/// Returns an instance of the keyutils-persistent credential builder.
///
/// If secret-service-with-keyutils is the default credential store,
/// this is called once when an entry is first created.
/// If keyutils-persistent is the default credential store, this is
/// called once when an entry is first created.
pub fn default_credential_builder() -> Box<CredentialBuilder> {
Box::new(KeyutilsPersistentCredentialBuilder {})
}
Expand All @@ -120,17 +157,94 @@ impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder {
self
}

/// Since this keystore keeps credentials in kernel memory,
/// they vanish on reboot
/// This keystore keeps credentials thanks to the inner secret-service store.
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::UntilDelete
}
}

/// Replace any Ambiguous error with a NoEntry one
fn ambigous_to_no_entry(err: Error) -> Error {
if let Error::Ambiguous(_) = err {
return Error::NoEntry;
};

err
}

#[cfg(test)]
mod tests {
use crate::credential::CredentialPersistence;
use crate::{Entry, Error};

use super::{default_credential_builder, KeyutilsPersistentCredential};

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilDelete
))
}

fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(
KeyutilsPersistentCredential::new_with_target,
service,
user,
)
}

#[test]
fn test_invalid_parameter() {
let credential = KeyutilsPersistentCredential::new_with_target(Some(""), "service", "user");
assert!(
matches!(credential, Err(Error::Invalid(_, _))),
"Created entry with empty target"
);
}

#[test]
fn test_empty_service_and_user() {
crate::tests::test_empty_service_and_user(entry_new);
}

#[test]
fn test_missing_entry() {
crate::tests::test_missing_entry(entry_new);
}

#[test]
fn test_empty_password() {
let entry = entry_new("empty password service", "empty password user");
assert!(
matches!(entry.set_password(""), Err(Error::Invalid(_, _))),
"Able to set empty password"
);
}

#[test]
fn test_round_trip_ascii_password() {
crate::tests::test_round_trip_ascii_password(entry_new);
}

#[test]
fn test_round_trip_non_ascii_password() {
crate::tests::test_round_trip_non_ascii_password(entry_new);
}

#[test]
fn test_round_trip_random_secret() {
crate::tests::test_round_trip_random_secret(entry_new);
}

#[test]
fn test_update() {
crate::tests::test_update(entry_new);
}

#[test]
fn test_noop_get_update_attributes() {
crate::tests::test_noop_get_update_attributes(entry_new);
}
}

0 comments on commit 6df3d93

Please sign in to comment.