diff --git a/README.md b/README.md index ff0ec5b..d5ea62a 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,14 @@ This crate allows clients to "bring their own credential store" by providing tra This crate provides built-in implementations of the following platform-specific credential stores: -* _Linux_: The DBus-based Secret Service and the kernel keyutils. +* _Linux_: The DBus-based Secret Service, the kernel keyutils, and a combo of the two. * _FreeBSD_, _OpenBSD_: The DBus-based Secret Service. * _macOS_, _iOS_: The local keychain. * _Windows_: The Windows Credential Manager. To enable the stores you want, you use features: there is one feature for each possibly-included credential store. If you specify a feature (e.g., `dbus-secret-service`) _and_ your target platform (e.g., `freebsd`) supports that credential store, it will be included as the default credential store in that build. That way you can have a build command that specifies a single credential store for each of your target platforms, and use that same build command for all targets. -If you don't enable any credential stores that are supported on a given platform, or you enable multiple credential stores for some platform, the _mock_ keystore will be the default on that platform. See the [developer docs](https://docs.rs/keyring/) for details of which features control the inclusion of which credential stores (and which platforms each credential store targets). +If you don't enable any credential stores that are supported on a given platform, the _mock_ keystore will be the default on that platform. See the [developer docs](https://docs.rs/keyring/) for details of which features control the inclusion of which credential stores. ### Platform-specific issues diff --git a/build-xplat-docs.sh b/build-xplat-docs.sh index 77d6c90..56b855b 100644 --- a/build-xplat-docs.sh +++ b/build-xplat-docs.sh @@ -1,5 +1,11 @@ #!/bin/bash -cargo doc --no-deps --features=linux-native --target aarch64-unknown-linux-musl $OPEN_DOCS -cargo doc --no-deps --features=windows-native --target aarch64-pc-windows-msvc $OPEN_DOCS -cargo doc --no-deps --features=apple-native --target aarch64-apple-darwin $OPEN_DOCS -cargo doc --no-deps --features=apple-native --target aarch64-apple-ios $OPEN_DOCS +if [[ "$OSTYPE" == "linux"* ]]; then + cargo doc --no-deps --features=linux-native-sync-persistent $OPEN_DOCS + cargo doc --no-deps --features=sync-secret-service $OPEN_DOCS + cargo doc --no-deps --features=linux-native $OPEN_DOCS +elif [[ "$OSTYPE" == "darwin"* ]]; then + cargo doc --no-deps --features=linux-native --target aarch64-unknown-linux-musl $OPEN_DOCS + cargo doc --no-deps --features=windows-native --target aarch64-pc-windows-msvc $OPEN_DOCS + cargo doc --no-deps --features=apple-native --target aarch64-apple-darwin $OPEN_DOCS + cargo doc --no-deps --features=apple-native --target aarch64-apple-ios $OPEN_DOCS +fi \ No newline at end of file diff --git a/src/keyutils_persistent.rs b/src/keyutils_persistent.rs index c2fcd66..d9ff4d9 100644 --- a/src/keyutils_persistent.rs +++ b/src/keyutils_persistent.rs @@ -1,11 +1,33 @@ /*! -# keyutils-persistent credential store - -This store is a combination of the [keyutils](crate::keyutils) store -backed up with a persistent [secret-service](crate::secret_service) -store. - +# Linux (keyutils) store with Secret Service backing + +This store, contributed by [@soywod](https://github.com/soywod), +uses the [keyutils module](crate::keyutils) as a cache +available to headless processes, while using the +[secret-service module](crate::secret_service) +to provide credential storage beyond reboot. +The expected usage pattern +for this module is as follows: + +- Processes that run on headless systems are built with `keyutils` support via the + `linux-native` feature of this crate. After each reboot, these processes + are either launched after the keyutils cache has been reloaded from the secret service, + or (if launched immediately) they wait until the keyutils cache has been reloaded. +- A headed "configuration" process is built with this module that allows its user + to configure the credentials needed by the headless processes. After each reboot, + this process unlocks the secret service (see both the keyutils and secret-service + module for information about how this can be done headlessly, if desired) and then + accesses each of the configured credentials (which loads them into keyutils). At + that point the headless clients can be started (or become active, if already started). + +This store works by creating a keyutils entry and a secret-service entry for +each of its entries. Because keyutils entries don't have attributes, entries +in this store don't expose attributes either. Because keyutils entries can't +store empty passwords/secrets, this store's entries can't either. + +See the documentation for the `keyutils` and `secret-service` modules if you +want details about how the underlying storage is handled. */ use log::debug; @@ -15,7 +37,7 @@ use super::credential::{ }; use super::error::{Error, Result}; use super::keyutils::KeyutilsCredential; -use super::secret_service::SsCredential; +use super::secret_service::{SsCredential, SsCredentialBuilder}; /// Representation of a keyutils-persistent credential. /// @@ -36,13 +58,14 @@ impl CredentialApi for KeyutilsPersistentCredential { /// 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 + /// secret-service. If the latter set fails, the former /// is reverted. 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) { + debug!("Failed set of secret-service: {err}; reverting keyutils"); match prev_secret { Ok(ref secret) => self.keyutils.set_secret(secret), Err(Error::NoEntry) => self.keyutils.delete_credential(), @@ -66,11 +89,11 @@ impl CredentialApi for KeyutilsPersistentCredential { return Ok(password); } Err(err) => { - debug!("cannot get password from keyutils: {err}, trying from secret service") + debug!("Failed get from keyutils: {err}; trying secret service") } } - let password = self.ss.get_password().map_err(ambigous_to_no_entry)?; + let password = self.ss.get_password().map_err(ambiguous_to_no_entry)?; self.keyutils.set_password(&password)?; Ok(password) @@ -87,11 +110,11 @@ impl CredentialApi for KeyutilsPersistentCredential { return Ok(secret); } Err(err) => { - debug!("cannot get secret from keyutils: {err}, trying from secret service") + debug!("Failed get from keyutils: {err}; trying secret service") } } - let secret = self.ss.get_secret().map_err(ambigous_to_no_entry)?; + let secret = self.ss.get_secret().map_err(ambiguous_to_no_entry)?; self.keyutils.set_secret(&secret)?; Ok(secret) @@ -121,9 +144,8 @@ 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`. + /// This just passes the arguments to the underlying two stores + /// and wraps their results with an entry that holds both. pub fn new_with_target(target: Option<&str>, service: &str, user: &str) -> Result { let ss = SsCredential::new_with_target(target, service, user)?; let keyutils = KeyutilsCredential::new_with_target(target, service, user)?; @@ -144,7 +166,7 @@ pub fn default_credential_builder() -> Box { } impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder { - /// Build an [KeyutilsPersistentCredential] for the given target, service, and user. + /// Build a [KeyutilsPersistentCredential] for the given target, service, and user. fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result> { Ok(Box::new(SsCredential::new_with_target( target, service, user, @@ -152,19 +174,21 @@ impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder { } /// Return the underlying builder object with an `Any` type so that it can - /// be downgraded to an [KeyutilsPersistentCredentialBuilder] for platform-specific processing. + /// be downgraded to a [KeyutilsPersistentCredentialBuilder] for platform-specific processing. fn as_any(&self) -> &dyn std::any::Any { self } - /// This keystore keeps credentials thanks to the inner secret-service store. + /// Return the persistence of this store. + /// + /// This store's persistence derives from that of the secret service. fn persistence(&self) -> CredentialPersistence { - CredentialPersistence::UntilDelete + SsCredentialBuilder {}.persistence() } } /// Replace any Ambiguous error with a NoEntry one -fn ambigous_to_no_entry(err: Error) -> Error { +fn ambiguous_to_no_entry(err: Error) -> Error { if let Error::Ambiguous(_) = err { return Error::NoEntry; }; @@ -174,18 +198,9 @@ fn ambigous_to_no_entry(err: Error) -> Error { #[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 - )) - } + use super::KeyutilsPersistentCredential; fn entry_new(service: &str, user: &str) -> Entry { crate::tests::entry_from_constructor( diff --git a/src/lib.rs b/src/lib.rs index 439e11b..5ad2db0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,6 @@ example, the macOS Keychain credential store is only included if the `"apple-nat feature is specified (and the crate is built with a macOS target). If no specified credential store features apply to a given platform, -or multiple credential store features apply to a given platform, this crate will use the (platform-independent) _mock_ credential store (see below) on that platform. There are no default features in this crate: you must specify explicitly which platform-specific @@ -78,6 +77,18 @@ Here are the available credential store features: - `linux-native`: Provides access to the `keyutils` storage on Linux. +- `linux-native-sync-persistent`: Uses both `keyutils` and `sync-secret-service` + (see below) for storage. See the docs for the `keyutils_persistent` + module for a full explanation of why both are used. Because this + store uses the `sync-secret-service`, you can use additional features related + to that store (described below). + +- `linux-native-async-persistent`: Uses both `keyutils` and `async-secret-service` + (see below) for storage. See the docs for the `keyutils_persistent` + module for a full explanation of why both are used. + Because this store uses the `async-secret-service`, you + must specify the additional features required by that store (described below). + - `sync-secret-service`: Provides access to the DBus-based [Secret Service](https://specifications.freedesktop.org/secret-service/latest/) storage on Linux, FreeBSD, and OpenBSD. This is a _synchronous_ keystore that provides @@ -99,17 +110,13 @@ Here are the available credential store features: installed on the user's machine, specify the `vendored` feature to statically link them with the built crate. -You cannot specify both the `sync-secret-service` and `async-secret-service` features; -this will produce a compile error. You must pick one or the other if you want to use -the secret service for credential storage. - The Linux platform is the only one for which this crate supplies multiple keystores: -secret-service and keyutils. The secret-service is the more widely used store, because -it provides persistence of credentials beyond reboot (which keyutils does not). However, -because secret-service relies on system UI for unlocking credentials, it often isn't -available on headless Linux installations, so keyutils is provided for those situations. -If you enable both the secret-service store and the keyutils store, the secret-service -store will be used as the default. +native (keyutils), sync or async secret service, and sync or async "combo" (both +keyutils and secret service). You cannot specify use of both sync and async +keystores; this will lead to a compile error. If you enable a combo keystore on Linux, +that will be the default keystore. If you don't enable a +combo keystore on Linux, but you do enable both the native and secret service keystores, +the secret service will be the default. ## Client-provided Credential Stores