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}