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);