linux_keyutils/
keyring.rs

1use crate::ffi::{self, KeyCtlOperation};
2use crate::utils::{CStr, CString, Vec};
3use crate::{Key, KeyError, KeyRingIdentifier, KeySerialId, KeyType, LinkNode, Links, Metadata};
4use core::convert::TryInto;
5
6/// Interface to perform keyring operations. Used to locate, create,
7/// search, add, and link/unlink keys to & from keyrings.
8#[derive(Debug, Copy, Clone, PartialEq, Eq)]
9pub struct KeyRing {
10    id: KeySerialId,
11}
12
13impl KeyRing {
14    /// Initialize a new [Key] object from the provided ID
15    pub(crate) fn from_id(id: KeySerialId) -> Self {
16        Self { id }
17    }
18
19    /// Obtain a KeyRing from its special identifier.
20    ///
21    /// If the create argument is true, then this method will attempt
22    /// to create the keyring. Otherwise it will only succeed if the
23    /// keyring already exists and is valid.
24    ///
25    /// Internally this uses KEYCTL_GET_KEYRING_ID to resolve a keyrings
26    /// real ID from the special identifier.
27    pub fn from_special_id(id: KeyRingIdentifier, create: bool) -> Result<Self, KeyError> {
28        let id: KeySerialId = ffi::keyctl!(
29            KeyCtlOperation::GetKeyRingId,
30            id as libc::c_ulong,
31            u32::from(create).into()
32        )?
33        .try_into()
34        .or(Err(KeyError::InvalidIdentifier))?;
35        Ok(Self { id })
36    }
37
38    /// Get the persistent keyring  (persistent-keyring(7)) of the current user
39    /// and link it to a specified keyring.
40    ///
41    /// If the call is successful, a link to the persistent keyring is added to the
42    /// keyring specified in the `link_with` argument.
43    ///
44    /// The caller must have write permission on the keyring.
45    ///
46    /// The persistent keyring will be created by the kernel if it does not yet exist.
47    ///
48    /// Each time the [KeyRing::get_persistent] operation is performed, the persistent
49    /// keyring will have its expiration timeout reset to the value in:
50    ///
51    ///    `/proc/sys/kernel/keys/persistent_keyring_expiry`
52    ///
53    /// Should the timeout be reached, the persistent keyring will be removed and
54    /// everything it pins can then be garbage collected.
55    ///
56    /// Persistent keyrings were added to Linux in kernel version 3.13.
57    pub fn get_persistent(link_with: KeyRingIdentifier) -> Result<Self, KeyError> {
58        let id: KeySerialId = ffi::keyctl!(
59            KeyCtlOperation::GetPersistent,
60            u32::MAX as _,
61            link_with as libc::c_ulong
62        )?
63        .try_into()
64        .or(Err(KeyError::InvalidIdentifier))?;
65        Ok(Self { id })
66    }
67
68    /// Obtain information describing the attributes of this keyring.
69    ///
70    /// The keyring must grant the caller view permission.
71    pub fn metadata(&self) -> Result<Metadata, KeyError> {
72        Metadata::from_id(self.id)
73    }
74
75    /// Creates or updates a key of the given type and description, instantiates
76    /// it with the payload of length plen, attaches it to the User keyring.
77    ///
78    /// If the destination keyring already contains a key that matches
79    /// the specified type and description, then, if the key type supports
80    /// it, that key will be updated rather than a new key being created;
81    /// if not, a new key (with a different ID) will be created and it will
82    /// displace the link to the extant key from the keyring.
83    pub fn add_key<D: AsRef<str> + ?Sized, S: AsRef<[u8]> + ?Sized>(
84        &self,
85        description: &D,
86        secret: &S,
87    ) -> Result<Key, KeyError> {
88        let id = ffi::add_key(
89            KeyType::User,
90            self.id.as_raw_id() as libc::c_ulong,
91            description.as_ref(),
92            Some(secret.as_ref()),
93        )?;
94        Ok(Key::from_id(id))
95    }
96
97    /// Search for a key in the keyring tree, starting with this keyring as the head,
98    /// returning its ID.
99    ///
100    /// The search is performed breadth-first and recursively.
101    ///
102    /// The source keyring must grant search permission to the caller. When
103    /// performing the recursive search, only keyrings that grant the caller search
104    /// permission will be searched. Only keys with for which the caller has
105    /// search permission can be found.
106    ///
107    /// If the key is found, its ID is returned as the function result.
108    pub fn search<D: AsRef<str> + ?Sized>(&self, description: &D) -> Result<Key, KeyError> {
109        // The provided description must be properly null terminated for the kernel
110        let description =
111            CString::new(description.as_ref()).or(Err(KeyError::InvalidDescription))?;
112
113        // Perform the raw syscall and validate that the result is a valid ID
114        let id: KeySerialId = ffi::keyctl!(
115            KeyCtlOperation::Search,
116            self.id.as_raw_id() as libc::c_ulong,
117            Into::<&'static CStr>::into(KeyType::User).as_ptr() as _,
118            description.as_ptr() as _,
119            0
120        )?
121        .try_into()
122        .or(Err(KeyError::InvalidIdentifier))?;
123
124        // Construct a key object from the ID
125        Ok(Key::from_id(id))
126    }
127
128    /// Obtain a list of the keys/keyrings linked to this keyring.
129    ///
130    /// This method allocates, but you can provide a maximum number of entries
131    /// to read. Each returned entry is 4 bytes.
132    ///
133    /// The keyring must either grant the caller read permission, or grant
134    /// the caller search permission.
135    pub fn get_links(&self, max: usize) -> Result<Links, KeyError> {
136        // Allocate the requested capacity
137        let mut buffer = Vec::<KeySerialId>::with_capacity(max);
138
139        // Perform the read
140        let len = ffi::keyctl!(
141            KeyCtlOperation::Read,
142            self.id.as_raw_id() as libc::c_ulong,
143            buffer.as_mut_ptr() as _,
144            buffer.capacity() as _
145        )? as usize;
146
147        // Set the size of the results
148        unsafe {
149            buffer.set_len(len / core::mem::size_of::<KeySerialId>());
150        }
151
152        // Remap the results to complete keys
153        Ok(buffer
154            .iter()
155            .filter_map(|&id| LinkNode::from_id(id).ok())
156            .collect())
157    }
158
159    /// Create a link from this keyring to a key.
160    ///
161    /// If a key with the same type and description is already linked in the keyring,
162    /// then that key is displaced from the keyring.
163    ///
164    /// Before  creating  the  link,  the  kernel  checks the nesting of the keyrings
165    /// and returns appropriate errors if the link would produce a cycle or if the
166    /// nesting of keyrings would be too deep (The limit on the nesting of keyrings is
167    /// determined by the kernel constant KEYRING_SEARCH_MAX_DEPTH, defined with the
168    /// value 6, and is necessary to prevent overflows on the kernel stack when
169    /// recursively searching keyrings).
170    ///
171    /// The caller must have link permission on the key being added and write
172    /// permission on the keyring.
173    pub fn link_key(&self, key: Key) -> Result<(), KeyError> {
174        _ = ffi::keyctl!(
175            KeyCtlOperation::Link,
176            key.get_id().as_raw_id() as _,
177            self.id.as_raw_id() as libc::c_ulong
178        )?;
179        Ok(())
180    }
181
182    /// Unlink a key from this keyring.
183    ///
184    /// If the key is not currently linked into the keyring, an error results. If the
185    /// last link to a key is removed, then that key will be scheduled for destruction.
186    ///
187    /// The caller must have write permission on the keyring from which the key is being
188    /// removed.
189    pub fn unlink_key(&self, key: Key) -> Result<(), KeyError> {
190        _ = ffi::keyctl!(
191            KeyCtlOperation::Unlink,
192            key.get_id().as_raw_id() as _,
193            self.id.as_raw_id() as libc::c_ulong
194        )?;
195        Ok(())
196    }
197
198    /// Clear the contents of (i.e., unlink all keys from) this keyring.
199    ///
200    /// The caller must have write permission on the keyring.
201    pub fn clear(&self) -> Result<(), KeyError> {
202        _ = ffi::keyctl!(KeyCtlOperation::Clear, self.id.as_raw_id() as libc::c_ulong)?;
203        Ok(())
204    }
205}
206
207#[cfg(test)]
208mod test {
209    use super::*;
210    use crate::{KeyPermissionsBuilder, Permission};
211
212    #[test]
213    fn test_from_special_id() {
214        // Test that a keyring that normally doesn't exist by default is
215        // created when called.
216        let ring = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap();
217        assert!(ring.id.as_raw_id() > 0);
218
219        // Test that a keyring that should already exist is returned
220        let ring = KeyRing::from_special_id(KeyRingIdentifier::User, false).unwrap();
221        assert!(ring.id.as_raw_id() > 0);
222    }
223
224    #[test]
225    fn test_get_persistent() {
226        // Test that a keyring that should already exist is returned
227        let user_ring = KeyRing::from_special_id(KeyRingIdentifier::User, false).unwrap();
228        assert!(user_ring.id.as_raw_id() > 0);
229
230        let user_perm_ring = KeyRing::get_persistent(KeyRingIdentifier::User).unwrap();
231        assert_ne!(user_ring, user_perm_ring);
232    }
233
234    #[test]
235    fn test_metadata() {
236        // Test that a keyring that normally doesn't exist by default is
237        // created when called.
238        let ring = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap();
239        assert!(ring.id.as_raw_id() > 0);
240
241        // Obtain and verify the info
242        let info = ring.metadata().unwrap();
243        assert_eq!(info.get_type(), KeyType::KeyRing);
244        assert_eq!(info.get_uid(), unsafe { libc::geteuid() });
245        assert_eq!(info.get_gid(), unsafe { libc::getegid() });
246        assert_eq!(info.get_description(), "_tid");
247    }
248
249    #[test]
250    fn test_search_existing_key() {
251        // Test that a keyring that normally doesn't exist by default is
252        // created when called.
253        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
254        let key = ring.add_key("test_search", b"data").unwrap();
255
256        // Ensure we have search permission on the key
257        let perms = KeyPermissionsBuilder::builder()
258            .posessor(Permission::ALL)
259            .user(Permission::ALL)
260            .build();
261
262        // Enforce perms
263        key.set_perms(perms).unwrap();
264
265        // Search should succeed
266        let result = ring.search("test_search").unwrap();
267
268        // Assert that the ID is the same
269        assert_eq!(key.get_id(), result.get_id());
270
271        // Invalidate the key
272        key.invalidate().unwrap();
273    }
274
275    #[test]
276    fn test_search_non_existing_key() {
277        // Test that a keyring that normally doesn't exist by default is
278        // created when called.
279        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
280
281        // Search should succeed
282        let result = ring.search("test_search_no_exist");
283
284        // Assert that the ID is the same
285        assert!(result.is_err());
286        assert_eq!(result.unwrap_err(), KeyError::KeyDoesNotExist);
287    }
288
289    #[test]
290    fn test_get_linked_items() {
291        // Test that a keyring that should already exist is returned
292        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
293        assert!(ring.id.as_raw_id() > 0);
294
295        // Add the key
296        let key = ring.add_key("test_read_key", b"test").unwrap();
297
298        // Obtain a list of the linked keys
299        let items = ring.get_links(200).unwrap();
300
301        // Assert that the key is in the ring
302        assert!(items.len() > 0);
303        assert!(items.contains(&key));
304
305        // Use the alternate reference to the key
306        let key_ref = items.get(&key).unwrap().as_key().unwrap();
307
308        // Invalidate the key
309        key_ref.invalidate().unwrap();
310
311        // Assert that the key is no longer on the ring
312        let items = ring.get_links(200).unwrap();
313        assert!(!items.contains(&key));
314    }
315}