Keep secrets in Credential Manager on Windows

The Secret Service store was keyring::Entry all the way down, and
keyring 4's v1 feature set — the one the workspace already asks for —
includes the Windows Credential Manager backend. So the Windows store
is the same implementation with its cfg widened, and the crate as a
target dependency. The one behavioural difference is that the
availability probe always succeeds there, which is correct: Credential
Manager is always present, so FR-NC-2's degraded mode does not arise.
Until now a Windows build compiled, started, and failed at sign-in
with the placeholder store's "no secret store is implemented".
This commit is contained in:
2026-09-12 07:34:08 +02:00
parent ef1154af94
commit beb822dced
2 changed files with 24 additions and 11 deletions
+5
View File
@@ -24,6 +24,11 @@ x11rb.workspace = true
wayland-client.workspace = true
wayland-protocols.workspace = true
# The same crate on Windows: its `v1` features include the Credential Manager
# backend, and `secrets.rs` is one implementation over both.
[target.'cfg(windows)'.dependencies]
keyring.workspace = true
[target.'cfg(target_os = "android")'.dependencies]
android-native-keyring-store.workspace = true
keyring-core.workspace = true
+19 -11
View File
@@ -100,14 +100,22 @@ pub trait SecretStore: Send + Sync {
///
/// Used by the two stores that have somewhere to file them; the placeholder
/// below has nothing to name, and a Windows build otherwise warns here.
#[cfg(any(all(unix, not(target_os = "android")), target_os = "android"))]
#[cfg(any(all(unix, not(target_os = "android")), windows, target_os = "android"))]
const SERVICE: &str = "DarkRoom";
/// Secret Service implementation (GNOME Keyring, KWallet via ksecretd).
#[cfg(all(unix, not(target_os = "android")))]
/// The `keyring`-backed store: Secret Service on Linux (GNOME Keyring,
/// KWallet via ksecretd), Credential Manager on Windows.
///
/// One implementation for both because `keyring::Entry` is the same API over
/// either, and its `v1` feature set already includes the Windows backend. The
/// difference is in what "unavailable" means: a Linux desktop may have no
/// secrets daemon, which FR-NC-2 treats as a stated degraded mode, while
/// Credential Manager is always present — so on Windows `is_available` is a
/// probe that always succeeds, and that is correct rather than optimistic.
#[cfg(any(all(unix, not(target_os = "android")), windows))]
pub struct PlatformSecretStore;
#[cfg(all(unix, not(target_os = "android")))]
#[cfg(any(all(unix, not(target_os = "android")), windows))]
impl PlatformSecretStore {
pub fn new() -> Self {
Self
@@ -118,14 +126,14 @@ impl PlatformSecretStore {
}
}
#[cfg(all(unix, not(target_os = "android")))]
#[cfg(any(all(unix, not(target_os = "android")), windows))]
impl Default for PlatformSecretStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(unix, not(target_os = "android")))]
#[cfg(any(all(unix, not(target_os = "android")), windows))]
impl SecretStore for PlatformSecretStore {
fn store(&self, secret_ref: &SecretRef, secret: &str) -> Result<(), SecretError> {
Self::entry(secret_ref)?
@@ -160,7 +168,7 @@ impl SecretStore for PlatformSecretStore {
}
}
#[cfg(all(unix, not(target_os = "android")))]
#[cfg(any(all(unix, not(target_os = "android")), windows))]
fn map_err(e: keyring::Error) -> SecretError {
match e {
keyring::Error::NoEntry => SecretError::NotFound,
@@ -265,24 +273,24 @@ fn map_err(e: keyring_core::Error) -> SecretError {
///
/// Failing loudly is deliberate: a silent no-op store would look like it
/// worked and then lose the credential.
#[cfg(not(any(all(unix, not(target_os = "android")), target_os = "android")))]
#[cfg(not(any(all(unix, not(target_os = "android")), windows, target_os = "android")))]
pub struct PlatformSecretStore;
#[cfg(not(any(all(unix, not(target_os = "android")), target_os = "android")))]
#[cfg(not(any(all(unix, not(target_os = "android")), windows, target_os = "android")))]
impl PlatformSecretStore {
pub fn new() -> Self {
Self
}
}
#[cfg(not(any(all(unix, not(target_os = "android")), target_os = "android")))]
#[cfg(not(any(all(unix, not(target_os = "android")), windows, target_os = "android")))]
impl Default for PlatformSecretStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(any(all(unix, not(target_os = "android")), target_os = "android")))]
#[cfg(not(any(all(unix, not(target_os = "android")), windows, target_os = "android")))]
impl SecretStore for PlatformSecretStore {
fn store(&self, _r: &SecretRef, _s: &str) -> Result<(), SecretError> {
Err(SecretError::Unavailable(