Skip to main content

evault_store_keyring/
lib.rs

1//! OS-keyring-backed implementation of `evault-core`'s
2//! [`SecretStore`](evault_core::traits::SecretStore) trait.
3//!
4//! [`OsKeyringSecretStore`] delegates to the platform's native credential
5//! store via the `keyring` crate:
6//!
7//! - **Windows**: Credential Manager (DPAPI).
8//! - **macOS**: Keychain.
9//! - **Linux/BSD**: D-Bus Secret Service (`gnome-keyring`, `KWallet`, …).
10//!
11//! Each variable's value is stored under the canonical service identifier
12//! `"evault"` keyed by the variable's UUID. The mapping is one credential
13//! per variable so platform tooling can audit individual secrets.
14//!
15//! # Error semantics
16//!
17//! Per the [`evault_core::traits::SecretStore`] contract:
18//! - `get` returns `Ok(None)` when no credential exists for the supplied
19//!   id. Platform "item not found" codes are the only path to this
20//!   `None`; every other backend error propagates as
21//!   [`evault_core::error::SecretError::Backend`] or
22//!   [`evault_core::error::SecretError::Unavailable`].
23//! - `delete` is idempotent: deleting an absent item is `Ok(())`.
24//! - `put` always either writes or returns an error — it never silently
25//!   fails.
26//!
27//! # Operational notes
28//!
29//! - **Single backend per process.** `keyring 4.x` keeps a single
30//!   process-wide active backend (`use_native_store`, `use_named_store`,
31//!   `release_store`). This crate initialises it once via
32//!   [`std::sync::OnceLock`]. **Do not** call any of those keyring-crate
33//!   functions yourself in the same process — a host that swaps the
34//!   backend later may route subsequent operations to an in-memory
35//!   `sample` store that does not persist.
36//! - **`Backend("ambiguous")` is a security signal.** Because every
37//!   variable lives at a fixed `("evault", <uuid>)` pair, an
38//!   ambiguity error means another application is writing to the same
39//!   namespace. Treat it as tampering, not as a routine backend error.
40//! - **Init result is sticky.** If the first call fails (e.g. the user
41//!   hasn't started gnome-keyring yet), subsequent calls in the same
42//!   process see the cached failure. A CLI re-launch starts fresh; a
43//!   daemon would need a future "reset" hook.
44//!
45//! # Examples
46//!
47//! ```ignore
48//! use evault_core::traits::SecretStore;
49//! use evault_core::crypto::{ExposeSecret, SecretString};
50//! use evault_core::model::VarId;
51//! use evault_store_keyring::OsKeyringSecretStore;
52//!
53//! let store = OsKeyringSecretStore::new().expect("init keyring");
54//! let id = VarId::new_v4();
55//! store.put(id, SecretString::from(String::from("hunter2"))).unwrap();
56//! let got = store.get(id).unwrap().expect("present");
57//! assert_eq!(got.expose_secret(), "hunter2");
58//! store.delete(id).unwrap();
59//! ```
60#![forbid(unsafe_code)]
61
62mod errors;
63mod store;
64
65pub use store::OsKeyringSecretStore;