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;