linux_keyutils_keyring_store/
cred.rs

1use super::error::KeyStoreError;
2use keyring_core::Error::NoStorageAccess;
3use keyring_core::api::CredentialApi;
4use keyring_core::{Credential, Error};
5use linux_keyutils::{KeyRing, KeyRingIdentifier};
6use std::sync::Arc;
7
8/// Representation of a keyutils credential.
9///
10/// Since the CredentialBuilderApi::build method does not provide
11/// an initial secret, and it is impossible to have 0-length keys,
12/// this representation holds a linux_keyutils::KeyRing instead
13/// of a linux_keyutils::Key.
14///
15/// The added benefit of this approach
16/// is that any call to get_password before set_password is done
17/// will result in a proper error as the key does not exist until
18/// set_password is called.
19#[derive(Debug, Clone)]
20pub struct Cred {
21    /// Host session keyring
22    pub session: KeyRing,
23    /// Host persistent keyring
24    pub persistent: Option<KeyRing>,
25    /// Description of the key entry
26    pub description: String,
27    /// Specifiers for the entry, if any
28    pub specifiers: Option<(String, String)>,
29}
30
31impl CredentialApi for Cred {
32    /// See the keyring-core API docs.
33    ///
34    /// This will overwrite the entry if it already exists since
35    /// it's using `add_key` under the hood.
36    ///
37    /// Returns an [Invalid](Error::Invalid) error if the password
38    /// is empty, because keyutils keys cannot have empty values.
39    fn set_secret(&self, secret: &[u8]) -> keyring_core::error::Result<()> {
40        if secret.is_empty() {
41            return Err(Error::Invalid(
42                "secret".to_string(),
43                "cannot be empty".to_string(),
44            ));
45        }
46        self.set(secret)?;
47        Ok(())
48    }
49
50    /// See the keyring-core API docs.
51    ///
52    /// This requires a call to `Key::read`.
53    fn get_secret(&self) -> keyring_core::error::Result<Vec<u8>> {
54        let buffer = self.get()?;
55        Ok(buffer)
56    }
57
58    /// See the keyring-core API docs.
59    ///
60    /// Under the hood this uses `Key::invalidate` to immediately
61    /// invalidate the key and prevent any further successful
62    /// searches.
63    ///
64    /// Note that the keyutils implementation uses caching,
65    /// and the caches take some time to clear,
66    /// so get_password may find a key that has been invalidated
67    /// if it's called within milliseconds of the invalidation
68    /// in *the same process* that deleted the key.
69    fn delete_credential(&self) -> keyring_core::error::Result<()> {
70        self.remove()?;
71        Ok(())
72    }
73
74    /// See the keyring-core API docs.
75    ///
76    /// Since this store has no ambiguity, entries are wrappers.
77    fn get_credential(&self) -> keyring_core::Result<Option<Arc<Credential>>> {
78        self.session
79            .search(&self.description)
80            .map_err(KeyStoreError::from)
81            .map_err(keyring_core::Error::from)?;
82        Ok(None)
83    }
84
85    /// See the keyring-core API docs.
86    ///
87    /// Specifiers are remembered at creation time if the description was not custom.
88    fn get_specifiers(&self) -> Option<(String, String)> {
89        self.specifiers.clone()
90    }
91
92    /// See the keyring-core API docs.
93    fn as_any(&self) -> &dyn std::any::Any {
94        self
95    }
96
97    /// See the keyring-core API docs.
98    fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        std::fmt::Debug::fmt(self, f)
100    }
101}
102
103impl Cred {
104    /// Create the platform credential for a Keyutils entry.
105    ///
106    /// An explicit target string is interpreted as the description to use for the entry.
107    /// If none is provided, then we concatenate the user and service in the string
108    /// `{delimiters[0]}{user}{delimiters[1]}{service}{delimiters[2]}`.
109    pub fn build_from_specifiers(
110        target: Option<&str>,
111        delimiters: &[String; 3],
112        service_no_dividers: bool,
113        service: &str,
114        user: &str,
115    ) -> keyring_core::error::Result<Self> {
116        // Construct the description with a URI-style description
117        let (description, specifiers) = match target {
118            Some(value) => (value.to_string(), None),
119            None => {
120                if service_no_dividers && service.contains(delimiters[1].as_str()) {
121                    return Err(Error::Invalid(
122                        "service".to_string(),
123                        "cannot contain delimiter".to_string(),
124                    ));
125                }
126                (
127                    format!(
128                        "{}{user}{}{service}{}",
129                        delimiters[0], delimiters[1], delimiters[2]
130                    ),
131                    Some((service.to_string(), user.to_string())),
132                )
133            }
134        };
135        if description.is_empty() {
136            return Err(Error::Invalid(
137                "description".to_string(),
138                "cannot be empty".to_string(),
139            ));
140        }
141
142        // Get the session keyring
143        let session = KeyRing::from_special_id(KeyRingIdentifier::Session, false)
144            .map_err(|e| NoStorageAccess(e.into()))?;
145
146        // Link the persistent keyring to the session
147        let persistent = KeyRing::get_persistent(KeyRingIdentifier::Session).ok();
148
149        Ok(Self {
150            session,
151            persistent,
152            description,
153            specifiers,
154        })
155    }
156
157    /// Internal method to retrieve the underlying secret
158    ///
159    /// Will search for and re-link the existing key to the session and
160    /// persistent keyrings to ensure the key doesn't time out.
161    fn get(&self) -> Result<Vec<u8>, KeyStoreError> {
162        // Verify that the key exists and is valid
163        let key = self.session.search(&self.description)?;
164
165        // Directly re-link to the session keyring
166        // If a logout occurred, it will only be linked to the
167        // persistent keyring and needs to be added again.
168        self.session.link_key(key)?;
169
170        // Directly re-link to the persistent keyring
171        // If it expired, it will only be linked to the
172        // session keyring and needs to be added again.
173        if let Some(keyring) = self.persistent {
174            keyring.link_key(key)?;
175        }
176
177        // Read in the key (making sure we have enough room)
178        let data = key.read_to_vec()?;
179        Ok(data)
180    }
181
182    /// Internal method to set the underlying secret
183    ///
184    /// Will add the key directly to the session and link it to the
185    /// persistent keyring when available.
186    fn set<T: AsRef<[u8]>>(&self, secret: T) -> Result<(), KeyStoreError> {
187        // Add to the session keyring
188        let key = self.session.add_key(&self.description, &secret)?;
189
190        // Directly link to the persistent keyring as well
191        if let Some(keyring) = self.persistent {
192            keyring.link_key(key).map_err(KeyStoreError)?;
193        }
194        Ok(())
195    }
196
197    /// Internal method to remove the underlying secret
198    ///
199    /// Performs a search and invalidates the key when found.
200    fn remove(&self) -> Result<(), KeyStoreError> {
201        // Verify that the key exists and is valid
202        let key = self.session.search(&self.description)?;
203
204        // Invalidate the key immediately
205        key.invalidate()?;
206        Ok(())
207    }
208}