Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide keyutils with persistence-after-reboot using secret-service #222

Merged
merged 20 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ jobs:
- "async-secret-service,async-io,crypto-rust"
- "async-secret-service,tokio,crypto-openssl"
- "async-secret-service,async-io,crypto-openssl"
- "sync-persistent-keyutils,crypto-rust"
- "sync-persistent-keyutils,crypto-openssl"
- "async-persistent-keyutils,tokio,crypto-rust"
- "async-persistent-keyutils,async-io,crypto-rust"
- "async-persistent-keyutils,tokio,crypto-openssl"
- "async-persistent-keyutils,async-io,crypto-openssl"
soywod marked this conversation as resolved.
Show resolved Hide resolved

steps:
- name: Install CI dependencies
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ linux-native = ["dep:linux-keyutils"]
apple-native = ["dep:security-framework"]
windows-native = ["dep:windows-sys", "dep:byteorder"]

sync-persistent-keyutils = ["dep:linux-keyutils", "sync-secret-service"]
soywod marked this conversation as resolved.
Show resolved Hide resolved
async-persistent-keyutils = ["dep:linux-keyutils", "async-secret-service"]
sync-secret-service = ["dep:dbus-secret-service"]
async-secret-service = ["dep:secret-service", "dep:zbus"]
crypto-rust = ["dbus-secret-service?/crypto-rust", "secret-service?/crypto-rust"]
Expand Down
123 changes: 123 additions & 0 deletions src/keyutils_persistent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*!

# keyutils-persistent credential store

TODO

*/
use super::credential::{
Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi, CredentialPersistence,
};
use super::error::{Error, Result};
use super::keyutils::KeyutilsCredential;
use super::secret_service::SsCredential;

#[derive(Debug, Clone)]
pub struct KeyutilsPersistentCredential {
keyutils: KeyutilsCredential,
ss: SsCredential,
}

impl CredentialApi for KeyutilsPersistentCredential {
fn set_password(&self, password: &str) -> Result<()> {
self.set_secret(password.as_bytes())
}

fn set_secret(&self, secret: &[u8]) -> Result<()> {
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)?;
return Err(err);
}

Ok(())
}
soywod marked this conversation as resolved.
Show resolved Hide resolved

fn get_password(&self) -> Result<String> {
if let Ok(password) = self.keyutils.get_password() {
return Ok(password);
}

let password = self.ss.get_password().map_err(ambigous_to_no_entry)?;
self.keyutils.set_password(&password)?;
soywod marked this conversation as resolved.
Show resolved Hide resolved

Ok(password)
}

fn get_secret(&self) -> Result<Vec<u8>> {
if let Ok(secret) = self.keyutils.get_secret() {
return Ok(secret);
}

let secret = self.ss.get_secret().map_err(ambigous_to_no_entry)?;
self.keyutils.set_secret(&secret)?;
soywod marked this conversation as resolved.
Show resolved Hide resolved

Ok(secret)
}

fn delete_credential(&self) -> Result<()> {
// TODO: log the error
soywod marked this conversation as resolved.
Show resolved Hide resolved
let _ = self.keyutils.delete_credential();
self.ss.delete_credential()?;
Ok(())
}

fn as_any(&self) -> &dyn std::any::Any {
self
}

fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}

impl KeyutilsPersistentCredential {
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 })
}
}
soywod marked this conversation as resolved.
Show resolved Hide resolved

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

/// Returns an instance of the secret-service-with-keyutils credential builder.
///
/// If secret-service-with-keyutils 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 {})
}

impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder {
/// Build an [KeyutilsPersistentCredential] for the given target, service, and user.
fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>> {
Ok(Box::new(SsCredential::new_with_target(
target, service, user,
)?))
}

/// Return the underlying builder object with an `Any` type so that it can
/// be downgraded to an [KeyutilsPersistentCredentialBuilder] for platform-specific processing.
fn as_any(&self) -> &dyn std::any::Any {
self
}

/// Since this keystore keeps credentials in kernel memory,
/// they vanish on reboot
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::UntilDelete
}
}

fn ambigous_to_no_entry(err: Error) -> Error {
if let Error::Ambiguous(_) = err {
return Error::NoEntry;
};

err
}
71 changes: 48 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,50 +182,74 @@ pub mod mock;
//
// can't use both sync and async secret service
//
#[cfg(all(feature = "sync-secret-service", feature = "async-secret-service"))]
compile_error!("This crate cannot use the secret-service both synchronously and asynchronously");
#[cfg(any(
all(feature = "sync-secret-service", feature = "async-secret-service"),
all(
feature = "sync-persistent-keyutils",
feature = "async-persistent-keyutils",
)
))]
compile_error!("This crate cannot use both the sync and async versions of any credential store");

//
// pick the *nix keystore
//

#[cfg(all(target_os = "linux", feature = "linux-native"))]
#[cfg(all(
soywod marked this conversation as resolved.
Show resolved Hide resolved
target_os = "linux",
any(
feature = "linux-native",
feature = "sync-persistent-keyutils",
feature = "async-persistent-keyutils",
)
))]
pub mod keyutils;
// use keyutils as default if secret-service is not available
#[cfg(all(
target_os = "linux",
feature = "linux-native",
not(any(feature = "sync-secret-service", feature = "async-secret-service"))
not(feature = "sync-secret-service"),
not(feature = "async-secret-service"),
))]
soywod marked this conversation as resolved.
Show resolved Hide resolved
pub use keyutils as default;

#[cfg(all(
any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"),
any(feature = "sync-secret-service", feature = "async-secret-service")
any(feature = "sync-secret-service", feature = "async-secret-service"),
))]
pub mod secret_service;
// use secret-service as default if it's available
#[cfg(all(
any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"),
any(feature = "sync-secret-service", feature = "async-secret-service"),
not(any(
feature = "sync-persistent-keyutils",
feature = "async-persistent-keyutils",
)),
))]
pub use secret_service as default;

// fallback to mock if neither keyutils nor secret service is available
#[cfg(any(
all(
target_os = "linux",
not(any(
feature = "linux-native",
feature = "sync-secret-service",
feature = "async-secret-service"
))
),
all(
any(target_os = "freebsd", target_os = "openbsd"),
not(any(feature = "sync-secret-service", feature = "async-secret-service"))
#[cfg(all(
target_os = "linux",
any(
feature = "sync-persistent-keyutils",
feature = "async-persistent-keyutils",
)
))]
pub mod keyutils_persistent;
#[cfg(all(
target_os = "linux",
any(
feature = "sync-persistent-keyutils",
feature = "async-persistent-keyutils",
),
))]
pub use keyutils_persistent as default;

// fallback to mock if neither keyutils nor secret service is available
#[cfg(all(
any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"),
not(feature = "linux-native"),
not(feature = "sync-secret-service"),
not(feature = "async-secret-service"),
))]
soywod marked this conversation as resolved.
Show resolved Hide resolved
pub use mock as default;

//
Expand All @@ -248,7 +272,6 @@ pub use mock as default;
//
// pick the Windows keystore
//

#[cfg(all(target_os = "windows", feature = "windows-native"))]
pub mod windows;
#[cfg(all(target_os = "windows", not(feature = "windows-native")))]
Expand Down Expand Up @@ -555,7 +578,9 @@ mod tests {
pub fn generate_random_string_of_len(len: usize) -> String {
// from the Rust Cookbook:
// https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html
use rand::{distributions::Alphanumeric, thread_rng, Rng};
#[allow(unused_imports)]
use rand::Rng;
use rand::{distributions::Alphanumeric, thread_rng};
soywod marked this conversation as resolved.
Show resolved Hide resolved
thread_rng()
.sample_iter(&Alphanumeric)
.take(len)
Expand Down