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(¤t, &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}