Skip to main content

keyring_core/
api.rs

1/*!
2
3# Platform-independent secure storage model
4
5This module defines a plug and play model for credential stores. The model comprises two
6traits: [CredentialStoreApi] for store-level operations and [CredentialApi] for
7entry-level operations.  These traits must be implemented in a thread-safe way, a
8requirement captured in the [CredentialStore] and [Credential] types that wrap them.
9 */
10use std::any::Any;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14use super::{Entry, Error, Result};
15
16/// The API that [credentials](Credential) implement.
17pub trait CredentialApi {
18    /// Set the entry's protected data to be the given string.
19    ///
20    /// This method has a default implementation in terms of
21    /// [set_secret](CredentialApi::set_secret), which see.
22    fn set_password(&self, password: &str) -> Result<()> {
23        self.set_secret(password.as_bytes())
24    }
25
26    /// Set the underlying credential's protected data to be the given byte array.
27    ///
28    /// - If the password cannot be stored in a credential,
29    ///   return an [Invalid](Error::Invalid) error.
30    /// - If the entry is a specifier, and there is no matching credential,
31    ///   create a matching credential and save the data in it.
32    /// - If the entry is a specifier, and there is more than one matching credential,
33    ///   return an [Ambiguous](Error::Ambiguous) error.
34    /// - If the entry is a wrapper, and the wrapped credential has been deleted,
35    ///   either recreate the wrapped credential and set its value or
36    ///   return a [NoEntry](Error::NoEntry) error.
37    /// - Otherwise, set the value of the single, matching credential.
38    ///
39    /// Note: If an entry is both a specifier and a wrapper, it's up to the store
40    /// whether to recreate a deleted credential or to fail with a NoEntry error.
41    fn set_secret(&self, secret: &[u8]) -> Result<()>;
42
43    /// Retrieve the protected data as a UTF-8 string from the underlying credential.
44    ///
45    /// This method has a default implementation in terms of
46    /// [get_secret](CredentialApi::get_secret), which see.
47    /// If the data in the credential is not valid UTF-8, the default implementation
48    /// returns a [BadEncoding](Error::BadEncoding) error containing the data.
49    fn get_password(&self) -> Result<String> {
50        let secret = self.get_secret()?;
51        super::error::decode_password(secret)
52    }
53
54    /// Retrieve the protected data as a byte array from the underlying credential.
55    ///
56    /// - If the entry is a specifier, and there is no matching credential,
57    ///   return a [NoEntry](Error::NoEntry) error.
58    /// - If the entry is a specifier, and there is more than one matching credential,
59    ///   return an [Ambiguous](Error::Ambiguous) error.
60    /// - If the entry is a wrapper, and the wrapped credential has been deleted,
61    ///   return a [NoEntry](Error::NoEntry) error.
62    /// - Otherwise, return the value of the single, matching credential.
63    fn get_secret(&self) -> Result<Vec<u8>>;
64
65    /// Return any store-specific decorations on this entry's credential.
66    ///
67    /// The expected error and success cases are the same as with
68    /// [get_secret](CredentialApi::get_secret), which see.
69    ///
70    /// For convenience, a default implementation of this method is
71    /// provided which doesn't return any attributes. Credential
72    /// store implementations which support attributes should
73    /// override this method.
74    fn get_attributes(&self) -> Result<HashMap<String, String>> {
75        // this should err in the same cases as get_secret, so first call that for effect
76        self.get_secret()?;
77        // if we got this far, return success with no attributes
78        Ok(HashMap::new())
79    }
80
81    /// Update the secure store attributes on this entry's credential.
82    ///
83    /// If the user supplies any attributes that cannot be updated,
84    /// return an appropriate [Invalid](Error::Invalid) error.
85    ///
86    /// Other expected error and success cases are the same as with
87    /// [get_secret](CredentialApi::get_secret), which see.
88    ///
89    /// For convenience, a default implementation of this method is
90    /// provided which returns a [NotSupportedByStore](Error::NotSupportedByStore) error.
91    fn update_attributes(&self, _: &HashMap<&str, &str>) -> Result<()> {
92        Err(Error::NotSupportedByStore(String::from(
93            "This store does not allow attribute updates",
94        )))
95    }
96
97    /// Delete the underlying credential.
98    ///
99    /// If the underlying credential doesn't exist, return
100    /// a [NoEntry](Error::NoEntry) error.
101    ///
102    /// If there is more than one matching credential,
103    /// return an [Ambiguous](Error::Ambiguous) error.
104    fn delete_credential(&self) -> Result<()>;
105
106    /// Return a wrapper for the underlying credential.
107    ///
108    /// If `self` is already a wrapper, you can return `None`
109    /// to give `self` back to the client.
110    /// Or you can return a new wrapper
111    /// for the same underlying credential. See the
112    /// [keyring-core wiki page](https://github.com/open-source-cooperative/keyring-rs/wiki/Keyring-Core#specifier-credentials-vs-wrapper-credentials)
113    /// for why the `None` option is available.
114    ///
115    /// If the underlying credential doesn't exist, return
116    /// a [NoEntry](Error::NoEntry) error.
117    ///
118    /// If there is more than one matching credential,
119    /// return an [Ambiguous](Error::Ambiguous) error.
120    fn get_credential(&self) -> Result<Option<Arc<Credential>>>;
121
122    /// Return the `<service, user>` pair for this credential, if any.
123    fn get_specifiers(&self) -> Option<(String, String)>;
124
125    /// Return the inner credential object cast to [Any].
126    ///
127    /// This call is used to expose the Debug trait for credentials.
128    fn as_any(&self) -> &dyn Any;
129
130    /// The Debug trait call for the object.
131    ///
132    /// This is used to implement the Debug trait on this type; it
133    /// allows generic code to provide debug printing as provided by
134    /// the underlying concrete object.
135    ///
136    /// We provide a (no-op) default implementation of this method.
137    fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        std::fmt::Debug::fmt(self.as_any(), f)
139    }
140}
141
142/// A thread-safe implementation of the [Credential API](CredentialApi).
143pub type Credential = dyn CredentialApi + Send + Sync;
144
145impl std::fmt::Debug for Credential {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        self.debug_fmt(f)
148    }
149}
150
151/// A descriptor for the lifetime of stored credentials, returned from
152/// a credential store's [persistence](CredentialStoreApi::persistence) call.
153///
154/// This enum may change even in minor and patch versions of the library, so it's
155/// marked as non-exhaustive.
156#[non_exhaustive]
157pub enum CredentialPersistence {
158    /// Credential storage is in the entry, so storage vanishes when the entry is dropped.
159    EntryOnly,
160    /// Credential storage is in process memory,
161    /// so storage vanishes when the process terminates
162    ProcessOnly,
163    /// Credential storage is in user-space memory, so storage vanishes when the user logs out
164    UntilLogout,
165    /// Credentials stored in kernel-space memory, so storage vanishes when the machine reboots
166    UntilReboot,
167    /// Credentials stored on disk, so storage vanishes when the credential is deleted
168    UntilDelete,
169    /// Placeholder for cases not (yet) handled here
170    Unspecified,
171}
172
173/// The API that [credential stores](CredentialStore) implement.
174pub trait CredentialStoreApi {
175    /// The name of the "vendor" that provides this store.
176    ///
177    /// This allows clients to conditionalize their code for specific vendors.
178    /// This string should not vary with versions of the store. It's recommended
179    /// that it include the crate URL for the module provider.
180    fn vendor(&self) -> String;
181
182    /// The ID of this credential store instance.
183    ///
184    /// IDs need not be unique across vendors or processes, but they
185    /// serve as instance IDs within a process.  If two credential store
186    /// instances in a process have the same vendor and id,
187    /// then they are the same instance.
188    ///
189    /// It's recommended that this include the version of the provider.
190    fn id(&self) -> String;
191
192    /// Create an entry specified by the given service and user,
193    /// perhaps with additional creation-time modifiers.
194    ///
195    /// The credential returned from this call must be a specifier,
196    /// meaning that it can be used to create a credential later
197    /// even if a matching credential existed in the store .
198    ///
199    /// This typically has no effect on the content of the underlying store.
200    /// A credential need not be persisted until its password is set.
201    fn build(
202        &self,
203        service: &str,
204        user: &str,
205        modifiers: Option<&HashMap<&str, &str>>,
206    ) -> Result<Entry>;
207
208    /// Search for credentials that match the given spec.
209    ///
210    /// Returns a list of the matching credentials.
211    ///
212    /// Should return an [Invalid](Error::Invalid) error if the spec is bad.
213    ///
214    /// The default implementation returns a
215    /// [NotSupportedByStore](Error::NotSupportedByStore) error; that is,
216    /// credential stores need not provide support for search.
217    fn search(&self, _spec: &HashMap<&str, &str>) -> Result<Vec<Entry>> {
218        let msg = "This store does not provide search capabilities";
219        Err(Error::NotSupportedByStore(msg.to_string()))
220    }
221
222    /// Return the inner store object cast to [Any].
223    ///
224    /// This call is used to expose the Debug trait for stores.
225    fn as_any(&self) -> &dyn Any;
226
227    /// The lifetime of credentials produced by this builder.
228    ///
229    /// A default implementation is provided for backward compatibility,
230    /// since this API was added in a minor release.  The default assumes
231    /// that keystores use disk-based credential storage.
232    fn persistence(&self) -> CredentialPersistence {
233        CredentialPersistence::UntilDelete
234    }
235
236    /// The Debug trait call for the object.
237    ///
238    /// This is used to implement the Debug trait on this type; it
239    /// allows generic code to provide debug printing as provided by
240    /// the underlying concrete object.
241    ///
242    /// We provide a (no-op) default implementation of this method.
243    fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        std::fmt::Debug::fmt(self.as_any(), f)
245    }
246}
247
248impl std::fmt::Debug for CredentialStore {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        self.debug_fmt(f)
251    }
252}
253
254/// A thread-safe implementation of the [CredentialBuilder API](CredentialStoreApi).
255pub type CredentialStore = dyn CredentialStoreApi + Send + Sync;
256
257#[cfg(test)]
258mod tests {
259    use super::{Any, Arc, CredentialStore, CredentialStoreApi, Entry, Error, HashMap, Result};
260
261    // minimal store that uses only the default impls;
262    // we use it to test the default impls only
263    struct MinimalStore;
264
265    impl CredentialStoreApi for MinimalStore {
266        fn vendor(&self) -> String {
267            unimplemented!()
268        }
269
270        fn id(&self) -> String {
271            unimplemented!()
272        }
273
274        fn build(&self, _: &str, _: &str, mods: Option<&HashMap<&str, &str>>) -> Result<Entry> {
275            match mods {
276                Some(mods) => Err(Error::Invalid("mods".to_string(), mods.len().to_string())),
277                None => Err(Error::Invalid("no-mods".to_string(), "0".to_string())),
278            }
279        }
280
281        fn as_any(&self) -> &dyn Any {
282            unimplemented!()
283        }
284    }
285
286    #[test]
287    fn test_entry_methods() {
288        let _ = env_logger::builder().is_test(true).try_init();
289        assert!(crate::get_default_store().is_none());
290        assert!(matches!(
291            Entry::new("test", "test"),
292            Err(Error::NoDefaultStore)
293        ));
294        assert!(matches!(
295            Entry::new_with_modifiers("test", "test", &HashMap::new()),
296            Err(Error::NoDefaultStore)
297        ));
298        assert!(matches!(
299            Entry::search(&HashMap::new()),
300            Err(Error::NoDefaultStore)
301        ));
302        let default: Arc<CredentialStore> = Arc::new(MinimalStore);
303        crate::set_default_store(default.clone());
304        let current = crate::get_default_store().unwrap();
305        assert!(Arc::ptr_eq(&current, &default));
306        let last = crate::unset_default_store().unwrap();
307        assert!(Arc::ptr_eq(&last, &default));
308        assert!(crate::get_default_store().is_none());
309        crate::set_default_store(default);
310        assert!(matches!(
311            Entry::new("test", "test"),
312            Err(Error::Invalid(x, y)) if x == "no-mods" && y == "0"
313        ));
314        assert!(matches!(
315            Entry::new_with_modifiers("test", "test", &HashMap::new()),
316            Err(Error::Invalid(x, y)) if x == "mods" && y == "0"
317        ));
318        assert!(matches!(
319            Entry::new_with_modifiers("test", "test", &HashMap::from([("k1", "v1"), ("k2", "v2")])),
320            Err(Error::Invalid(x, y)) if x == "mods" && y == "2"
321        ));
322        assert!(matches!(
323            Entry::search(&HashMap::new()),
324            Err(Error::NotSupportedByStore(_))
325        ));
326        crate::unset_default_store();
327        assert!(crate::get_default_store().is_none());
328    }
329}