keyring_core/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2/*!
3
4# Keyring-core
5
6This crate provides a cross-platform library that supports storage and retrieval
7of passwords (or other secrets) in a variety of secure credential stores.
8Please see
9[this document](https://github.com/open-source-cooperative/keyring-rs/wiki/Keyring)
10for an introduction to the keyring ecosystem and
11[this document](https://github.com/open-source-cooperative/keyring-rs/wiki/Keyring-Core)
12for a comprehensive treatment of this crate's APIs.
13
14This crate provides two cross-platform credential stores. These
15are provided to support client testing and as a guide for developers who
16would like to build keyring-compatible credential store modules. The
17stores in this crate are explicitly _not_ warranted to be either secure or robust.
18See the [mock] and [sample] modules for details. (Note: the [sample]
19module is only built if the `sample` feature is specified.)
20
21## Thread Safety
22
23While this crate's code is thread-safe,
24and requires credential store objects
25to be both Send and Sync, the underlying credential
26stores may not handle access to a single credential
27from different threads reliably.
28See the documentation of each credential store for details.
29 */
30
31use log::debug;
32use std::collections::HashMap;
33use std::sync::Arc;
34
35pub mod api;
36pub mod attributes;
37pub mod error;
38
39pub mod mock;
40
41#[cfg(feature = "sample")]
42pub mod sample;
43
44pub use api::{Credential, CredentialPersistence, CredentialStore};
45pub use error::{Error, Result};
46
47#[derive(Default, Debug)]
48struct DefaultStore {
49 inner: Option<Arc<CredentialStore>>,
50}
51
52static DEFAULT_STORE: std::sync::RwLock<DefaultStore> =
53 std::sync::RwLock::new(DefaultStore { inner: None });
54
55/// Set the credential store used by default to create entries.
56///
57/// This is meant for use by clients who use one credential store.
58/// If you are using multiple credential stores and want
59/// precise control over which credential is in which store,
60/// you may prefer to have your store build entries directly.
61///
62/// This will block waiting for all other threads currently creating entries
63/// to complete what they are doing. It's really meant to be called
64/// at startup before creating any entries.
65pub fn set_default_store(new: Arc<CredentialStore>) {
66 debug!("setting the default credential store to {new:?}");
67 let mut guard = DEFAULT_STORE
68 .write()
69 .expect("Poisoned RwLock in keyring_core::set_default_store: please report a bug!");
70 guard.inner = Some(new);
71}
72
73/// Get the default credential store.
74pub fn get_default_store() -> Option<Arc<CredentialStore>> {
75 debug!("getting the default credential store");
76 let guard = DEFAULT_STORE
77 .read()
78 .expect("Poisoned RwLock in keyring_core::get_default_store: please report a bug!");
79 guard.inner.clone()
80}
81
82/// Release the default credential store.
83///
84/// This returns the old value for the default credential store
85/// and forgets what it was. Since the default credential store
86/// is kept in a static variable, not releasing it will cause
87/// your credential store never to be released, which may have
88/// unintended side effects.
89pub fn unset_default_store() -> Option<Arc<CredentialStore>> {
90 debug!("unsetting the default credential store");
91 let mut guard = DEFAULT_STORE
92 .write()
93 .expect("Poisoned RwLock in keyring_core::unset_default_store: please report a bug!");
94 guard.inner.take()
95}
96
97fn build_default_credential(
98 service: &str,
99 user: &str,
100 attrs: Option<&HashMap<&str, &str>>,
101) -> Result<Entry> {
102 let guard = DEFAULT_STORE
103 .read()
104 .expect("Poisoned RwLock in keyring-core::build_default_credential: please report a bug!");
105 match guard.inner.as_ref() {
106 Some(store) => store.build(service, user, attrs),
107 None => Err(Error::NoDefaultStore),
108 }
109}
110
111/// A named entry in a credential store.
112#[derive(Debug)]
113pub struct Entry {
114 inner: Arc<Credential>,
115}
116
117impl Entry {
118 /// Create an entry for the given `service` and `user`.
119 ///
120 /// The default credential builder is used.
121 ///
122 /// # Errors
123 ///
124 /// Returns an [Invalid][Error::Invalid] error
125 /// if the `service` or `user` values are not
126 /// acceptable to the default credential store.
127 ///
128 /// Returns a [NoDefaultStore][Error::NoDefaultStore] error
129 /// if the default credential store has not been set.
130 pub fn new(service: &str, user: &str) -> Result<Entry> {
131 debug!("creating entry with service {service}, user {user}");
132 let entry = build_default_credential(service, user, None)?;
133 debug!("created entry {:?}", entry.inner);
134 Ok(entry)
135 }
136
137 /// Create an entry for the given `service` and `user`, passing store-specific modifiers.
138 ///
139 /// The default credential builder is used.
140 ///
141 /// See the documentation for each credential store to understand what
142 /// modifiers may be specified for that store.
143 ///
144 /// # Errors
145 ///
146 /// Returns an [Invalid][Error::Invalid] error
147 /// if the `service`, `user`, or `modifier` pairs are not
148 /// acceptable to the default credential store.
149 ///
150 /// Returns a [NoDefaultStore][Error::NoDefaultStore] error
151 /// if the default credential store has not been set.
152 pub fn new_with_modifiers(
153 service: &str,
154 user: &str,
155 modifiers: &HashMap<&str, &str>,
156 ) -> Result<Entry> {
157 debug!("creating entry with service {service}, user {user}, and mods {modifiers:?}");
158 let entry = build_default_credential(service, user, Some(modifiers))?;
159 debug!("created entry {:?}", entry.inner);
160 Ok(entry)
161 }
162
163 /// Create an entry that wraps a pre-existing credential. The credential can
164 /// be from any credential store.
165 pub fn new_with_credential(credential: Arc<Credential>) -> Entry {
166 debug!("create entry wrapping {credential:?}");
167 Entry { inner: credential }
168 }
169
170 /// Search for credentials, returning entries that wrap any found.
171 ///
172 /// The default credential store is searched.
173 /// See the documentation of each credential store for how searches are specified.
174 ///
175 /// # Errors
176 ///
177 /// Returns an [Invalid][Error::Invalid] error
178 /// if the `spec` value is not acceptable to the default credential store.
179 ///
180 /// Returns a [NoDefaultStore][Error::NoDefaultStore] error
181 /// if the default credential store has not been set.
182 pub fn search(spec: &HashMap<&str, &str>) -> Result<Vec<Entry>> {
183 debug!("searching for {spec:?}");
184 let guard = DEFAULT_STORE.read().expect(
185 "Poisoned RwLock in keyring-core::search_for_credentials: please report a bug!",
186 );
187 match guard.inner.as_ref() {
188 Some(store) => store.search(spec),
189 None => Err(Error::NoDefaultStore),
190 }
191 }
192
193 /// Set the password for this entry.
194 ///
195 /// If a credential for this entry already exists in the store,
196 /// this will update its password. Otherwise, a new credential
197 /// will be created to store the password.
198 ///
199 /// # Errors
200 ///
201 /// If this entry is a specifier,
202 /// and there is more than one matching credential in the store,
203 /// returns an [Ambiguous](Error::Ambiguous) error.
204 ///
205 /// If this entry is a wrapper, and the
206 /// underlying credential has been deleted,
207 /// may return a [NoEntry](Error::NoEntry) error.
208 ///
209 /// If a credential cannot store the given password (not
210 /// all stores support empty passwords, and some have length limits),
211 /// then an [Invalid](Error::Invalid) error is returned.
212 pub fn set_password(&self, password: &str) -> Result<()> {
213 debug!("set password for entry {:?}", self.inner);
214 self.inner.set_password(password)
215 }
216
217 /// Set the secret for this entry.
218 ///
219 /// If a credential for this entry already exists in the store,
220 /// this will update its secret. Otherwise, a new credential
221 /// will be created to store the secret.
222 ///
223 /// # Errors
224 ///
225 /// If this entry is a specifier,
226 /// and there is more than one matching credential in the store,
227 /// returns an [Ambiguous](Error::Ambiguous) error.
228 ///
229 /// If this entry is a wrapper, and the
230 /// underlying credential has been deleted,
231 /// may return a [NoEntry](Error::NoEntry) error.
232 ///
233 /// If a credential cannot store the given password (not
234 /// all stores support empty passwords, and some have length limits),
235 /// then an [Invalid](Error::Invalid) error is returned.
236 pub fn set_secret(&self, secret: &[u8]) -> Result<()> {
237 debug!("set secret for entry {:?}", self.inner);
238 self.inner.set_secret(secret)
239 }
240
241 /// Retrieve the password saved for this entry.
242 ///
243 /// # Errors
244 ///
245 /// If this entry is a specifier,
246 /// and there is no matching credential in the store,
247 /// returns a [NoEntry](Error::NoEntry) error.
248 ///
249 /// If this entry is a specifier,
250 /// and there is more than one matching credential in the store,
251 /// returns an [Ambiguous](Error::Ambiguous) error.
252 ///
253 /// If this entry is a wrapper,
254 /// the underlying credential has been deleted,
255 /// and the store cannot recreate it,
256 /// returns a [NoEntry](Error::NoEntry) error.
257 ///
258 /// Will return a [BadEncoding](Error::BadEncoding) error
259 /// containing the data as a byte array if the password is
260 /// not a valid UTF-8 string.
261 pub fn get_password(&self) -> Result<String> {
262 debug!("get password from entry {:?}", self.inner);
263 self.inner.get_password()
264 }
265
266 /// Retrieve the secret saved for this entry.
267 ///
268 /// # Errors
269 ///
270 /// If this entry is a specifier,
271 /// and there is no matching credential in the store,
272 /// returns a [NoEntry](Error::NoEntry) error.
273 ///
274 /// If this entry is a specifier,
275 /// and there is more than one matching credential in the store,
276 /// returns an [Ambiguous](Error::Ambiguous) error.
277 ///
278 /// If this entry is a wrapper,
279 /// and the underlying credential has been deleted,
280 /// returns a [NoEntry](Error::NoEntry) error.
281 pub fn get_secret(&self) -> Result<Vec<u8>> {
282 debug!("get secret from entry {:?}", self.inner);
283 self.inner.get_secret()
284 }
285
286 /// Get the store-specific decorations on this entry's credential.
287 ///
288 /// See the documentation for each credential store
289 /// for details of what decorations are supported
290 /// and how they are returned.
291 ///
292 /// # Errors
293 ///
294 /// If this entry is a specifier,
295 /// and there is no matching credential in the store,
296 /// returns a [NoEntry](Error::NoEntry) error.
297 ///
298 /// If this entry is a specifier,
299 /// and there is more than one matching credential in the store,
300 /// returns an [Ambiguous](Error::Ambiguous) error.
301 ///
302 /// If this entry is a wrapper,
303 /// and the underlying credential has been deleted,
304 /// returns a [NoEntry](Error::NoEntry) error.
305 pub fn get_attributes(&self) -> Result<HashMap<String, String>> {
306 debug!("get attributes from entry {:?}", self.inner);
307 self.inner.get_attributes()
308 }
309
310 /// Update the store-specific decorations on this entry's credential.
311 ///
312 /// See the documentation for each credential store
313 /// for details of what decorations can be updated
314 /// and how updates are expressed.
315 ///
316 /// # Errors
317 ///
318 /// If one of the attributes supplied is not valid for the underlying store,
319 /// returns an [Invalid](Error::Invalid) error.
320 ///
321 /// If this entry is a specifier,
322 /// and there is no matching credential in the store,
323 /// returns a [NoEntry](Error::NoEntry) error.
324 ///
325 /// If this entry is a specifier,
326 /// and there is more than one matching credential in the store,
327 /// returns an [Ambiguous](Error::Ambiguous) error.
328 ///
329 /// If this entry is a wrapper,
330 /// and the underlying credential has been deleted,
331 /// returns a [NoEntry](Error::NoEntry) error.
332 pub fn update_attributes(&self, attributes: &HashMap<&str, &str>) -> Result<()> {
333 debug!(
334 "update attributes for entry {:?} from map {attributes:?}",
335 self.inner
336 );
337 self.inner.update_attributes(attributes)
338 }
339
340 /// Delete the matching credential for this entry.
341 ///
342 /// This call does _not_ affect the lifetime of the [Entry]
343 /// structure, only that of the underlying credential.
344 ///
345 /// # Errors
346 ///
347 /// If this entry is a specifier,
348 /// and there is no matching credential in the store,
349 /// returns a [NoEntry](Error::NoEntry) error.
350 ///
351 /// If this entry is a specifier,
352 /// and there is more than one matching credential in the store,
353 /// returns an [Ambiguous](Error::Ambiguous) error.
354 ///
355 /// If this entry is a wrapper,
356 /// and the underlying credential has been deleted,
357 /// returns a [NoEntry](Error::NoEntry) error.
358 pub fn delete_credential(&self) -> Result<()> {
359 debug!("delete entry {:?}", self.inner);
360 self.inner.delete_credential()
361 }
362
363 /// Get a wrapper for the currently matching credential.
364 ///
365 /// # Errors
366 ///
367 /// If this entry is a specifier,
368 /// and there is no matching credential in the store,
369 /// returns a [NoEntry](Error::NoEntry) error.
370 ///
371 /// If this entry is a specifier,
372 /// and there is more than one matching credential in the store,
373 /// returns an [Ambiguous](Error::Ambiguous) error.
374 ///
375 /// If this entry is a wrapper,
376 /// and the underlying credential has been deleted,
377 /// returns a [NoEntry](Error::NoEntry) error.
378 pub fn get_credential(&self) -> Result<Entry> {
379 debug!("get credential for entry {:?}", self.inner);
380 match self.inner.get_credential() {
381 Ok(Some(inner)) => Ok(Entry { inner }),
382 Ok(None) => Ok(Entry {
383 inner: self.inner.clone(),
384 }),
385 Err(e) => Err(e),
386 }
387 }
388
389 /// Get the `<service, user>` pair for this entry, if any.
390 pub fn get_specifiers(&self) -> Option<(String, String)> {
391 self.inner.get_specifiers()
392 }
393
394 /// Return a reference to the inner store-specific object in this entry.
395 ///
396 /// The reference is of the [Any](std::any::Any) type, so it can be
397 /// downgraded to a concrete object for the containing store.
398 pub fn as_any(&self) -> &dyn std::any::Any {
399 self.inner.as_any()
400 }
401}
402
403#[cfg(doctest)]
404doc_comment::doctest!("../README.md", readme);