Skip to main content

keyring_manager/
lib.rs

1//! # Keyring library
2//!
3//! Allows for setting and getting passwords on Linux, OSX, Windows, and Android
4#![deny(clippy::all)]
5
6use log::*;
7use std::path::Path;
8
9mod error;
10mod escape;
11mod insecure;
12
13pub use error::{KeyringError, Result};
14use escape::*;
15use insecure::InsecureKeyringManager;
16
17/////////////////////////////////////////////////////////////////////////
18
19use cfg_if::*;
20cfg_if! {
21    if #[cfg(target_os = "linux")] {
22        mod linux;
23        use linux::LinuxKeyringManager as PlatformKeyringManager;
24    } else if #[cfg(target_os = "windows")] {
25        mod windows;
26        use windows::WindowsKeyringManager as PlatformKeyringManager;
27    } else if #[cfg(target_os = "macos")] {
28        mod macos;
29        use macos::MacosKeyringManager as PlatformKeyringManager;
30    } else if #[cfg(target_os = "ios")] {
31        mod ios;
32        use ios::IosKeyringManager as PlatformKeyringManager;
33    } else if #[cfg(target_os = "android")] {
34        pub(crate) mod android;
35        use android::AndroidKeyringManager as PlatformKeyringManager;
36        pub use android::AndroidKeyringContext;
37    } else {
38        struct PlatformKeyringManager {}
39
40        impl PlatformKeyringManager {
41            pub fn with_keyring<F, T>(&self, _service: &str, _key: &str, _func: F) -> Result<T>
42            where
43                F: FnOnce(&mut dyn Keyring) -> Result<T>,
44            {
45                Err(KeyringError::NoBackendFound)
46            }
47
48            pub fn list_keys(&self, _service: &str) -> Result<Vec<String>> {
49                Err(KeyringError::NoBackendFound)
50            }
51        }
52    }
53}
54/////////////////////////////////////////////////////////////////////////
55
56/// A single keyring entry, bound to one (application, service, key) triple.
57///
58/// A backend reports [`KeyringError::NoPasswordFound`] only when it established that nothing is
59/// stored. On iOS a store that could not be read reports [`KeyringError::Locked`] instead, so a
60/// caller may treat absence as an invitation to write.
61pub trait Keyring {
62    /// Store a value, overwriting any existing one. Never reports absence.
63    fn set_value(&mut self, value: &str) -> Result<()>;
64    /// Read the stored value, or [`KeyringError::NoPasswordFound`] if absent.
65    fn get_value(&self) -> Result<String>;
66    /// Delete the stored value, or [`KeyringError::NoPasswordFound`] if absent.
67    fn delete_value(&mut self) -> Result<()>;
68}
69
70enum KeyringManagerKind {
71    #[allow(dead_code)]
72    Secure(PlatformKeyringManager),
73    Insecure(InsecureKeyringManager),
74}
75
76pub struct KeyringManager {
77    kind: KeyringManagerKind,
78}
79
80impl KeyringManager {
81    cfg_if! {
82        if #[cfg(all(target_os = "macos", feature = "macos-specify-keychain"))] {
83            /// Open a secure keyring backed by the OS keychain at a specific path (macOS only).
84            pub fn new_secure_with_path(application: &str, path: &Path) -> Result<Self> {
85                Ok(KeyringManager {
86                    kind: KeyringManagerKind::Secure(PlatformKeyringManager::with_path(application, path)?),
87                })
88            }
89        }
90    }
91    cfg_if! {
92        if #[cfg(target_os = "android")] {
93            /// Open a secure keyring backed by the OS keystore (Android).
94            pub fn new_secure(application: &str, context: AndroidKeyringContext) -> Result<Self> {
95                Ok(KeyringManager {
96                    kind: KeyringManagerKind::Secure(PlatformKeyringManager::new(application, context)?),
97                })
98            }
99        } else if #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos", target_os = "ios"))] {
100            /// Open a secure keyring backed by the OS keychain/credential store.
101            pub fn new_secure(application: &str) -> Result<Self> {
102                Ok(KeyringManager {
103                    kind: KeyringManagerKind::Secure(PlatformKeyringManager::new(application)?),
104                })
105            }
106        } else {
107            /// No secure backend exists on this platform.
108            pub fn new_secure(_application: &str) -> Result<Self> {
109                Err(KeyringError::NoBackendFound)
110            }
111        }
112    }
113
114    /// Open an insecure keyring backed by an on-disk file (fallback when no OS backend is available).
115    pub fn new_insecure(application: &str, insecure_keyring_file: &Path) -> Result<Self> {
116        Ok(KeyringManager {
117            kind: KeyringManagerKind::Insecure(InsecureKeyringManager::new(
118                application,
119                insecure_keyring_file,
120            )?),
121        })
122    }
123
124    /// Run a closure against the keyring entry for (service, key).
125    pub fn with_keyring<F, T>(&self, service: &str, key: &str, f: F) -> Result<T>
126    where
127        F: FnOnce(&mut dyn Keyring) -> Result<T>,
128    {
129        match &self.kind {
130            KeyringManagerKind::Secure(pkm) => pkm.with_keyring(service, key, f),
131            KeyringManagerKind::Insecure(ikm) => ikm.with_keyring(service, key, f),
132        }
133    }
134
135    /// List the key names stored under a service, in no particular order. An unknown service is
136    /// an empty list, not an error.
137    ///
138    /// Enumeration is the one call that cannot prove absence: on iOS a locked device omits items
139    /// it cannot decrypt, so a short list is not evidence that a key is unset.
140    pub fn list_keys(&self, service: &str) -> Result<Vec<String>> {
141        match &self.kind {
142            KeyringManagerKind::Secure(pkm) => pkm.list_keys(service),
143            KeyringManagerKind::Insecure(ikm) => ikm.list_keys(service),
144        }
145    }
146}
147
148pub(crate) fn map_to_generic<E: std::fmt::Display>(e: E) -> KeyringError {
149    KeyringError::Generic(format!("{}", e))
150}
151
152pub(crate) fn map_log_err<T: std::error::Error>(e: T) -> T {
153    error!("{}", e);
154    e
155}
156
157cfg_if! {
158    if #[cfg(test)] {
159        mod tests;
160    } else if #[cfg(feature="keyring_manager_android_tests")] {
161        pub mod tests;
162    } else if #[cfg(feature="keyring_manager_ios_tests")] {
163        pub mod tests;
164    }
165}