Skip to main content

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 [KeyRing] 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 description and User type,
76    /// instantiates it with the payload of length plen, attaches it to the
77    /// keyring.
78    ///
79    /// If the destination keyring already contains a key that matches
80    /// the specified type and description, then, if the key type supports
81    /// it, that key will be updated rather than a new key being created;
82    /// if not, a new key (with a different ID) will be created and it will
83    /// displace the link to the extant key from the keyring.
84    pub fn add_key<D: AsRef<str> + ?Sized, S: AsRef<[u8]> + ?Sized>(
85        &self,
86        description: &D,
87        secret: &S,
88    ) -> Result<Key, KeyError> {
89        let id = ffi::add_key(
90            KeyType::User,
91            self.id.as_raw_id() as libc::c_ulong,
92            description.as_ref(),
93            Some(secret.as_ref()),
94        )?;
95        Ok(Key::from_id(id))
96    }
97
98    /// Attempts to find a key of the given type with a description that
99    /// matches the specified description. If such a key could not be found,
100    /// then the key is optionally created.
101    ///
102    /// If the key is found or created, it is attached it to the keyring
103    /// and returns the key's serial number.
104    ///
105    /// If the key is not found and callout info is empty then the call
106    /// fails with the error ENOKEY.
107    ///
108    /// If the key is not found and callout info is not empty, then the
109    /// kernel attempts to invoke a user-space program to instantiate the
110    /// key.
111    pub fn request_key<D: AsRef<str> + ?Sized, C: AsRef<str> + ?Sized>(
112        &self,
113        description: &D,
114        callout: Option<&C>,
115    ) -> Result<Key, KeyError> {
116        let id = ffi::request_key(
117            KeyType::User,
118            self.id.as_raw_id() as libc::c_ulong,
119            description.as_ref(),
120            callout.map(|c| c.as_ref()),
121        )?;
122        Ok(Key::from_id(id))
123    }
124
125    /// Search for a key in the keyring tree, starting with this keyring as the head,
126    /// returning its ID.
127    ///
128    /// The search is performed breadth-first and recursively.
129    ///
130    /// The source keyring must grant search permission to the caller. When
131    /// performing the recursive search, only keyrings that grant the caller search
132    /// permission will be searched. Only keys with for which the caller has
133    /// search permission can be found.
134    ///
135    /// If the key is found, its ID is returned as the function result.
136    pub fn search<D: AsRef<str> + ?Sized>(&self, description: &D) -> Result<Key, KeyError> {
137        // The provided description must be properly null terminated for the kernel
138        let description =
139            CString::new(description.as_ref()).or(Err(KeyError::InvalidDescription))?;
140
141        // Perform the raw syscall and validate that the result is a valid ID
142        let id: KeySerialId = ffi::keyctl!(
143            KeyCtlOperation::Search,
144            self.id.as_raw_id() as libc::c_ulong,
145            Into::<&'static CStr>::into(KeyType::User).as_ptr() as _,
146            description.as_ptr() as _,
147            0
148        )?
149        .try_into()
150        .or(Err(KeyError::InvalidIdentifier))?;
151
152        // Construct a key object from the ID
153        Ok(Key::from_id(id))
154    }
155
156    /// Obtain a list of the keys/keyrings linked to this keyring.
157    ///
158    /// This method allocates, but you can provide a maximum number of entries
159    /// to read. Each returned entry is 4 bytes.
160    ///
161    /// The keyring must either grant the caller read permission, or grant
162    /// the caller search permission.
163    pub fn get_links(&self, max: usize) -> Result<Links, KeyError> {
164        // Allocate the requested capacity
165        let mut buffer = Vec::<KeySerialId>::with_capacity(max);
166
167        // Perform the read
168        let len = ffi::keyctl!(
169            KeyCtlOperation::Read,
170            self.id.as_raw_id() as libc::c_ulong,
171            buffer.as_mut_ptr() as _,
172            buffer.capacity() as _
173        )? as usize;
174
175        // Set the size of the results
176        unsafe {
177            buffer.set_len(len / core::mem::size_of::<KeySerialId>());
178        }
179
180        // Remap the results to complete keys
181        Ok(buffer
182            .iter()
183            .filter_map(|&id| LinkNode::from_id(id).ok())
184            .collect())
185    }
186
187    /// Create a link from this keyring to a key.
188    ///
189    /// If a key with the same type and description is already linked in the keyring,
190    /// then that key is displaced from the keyring.
191    ///
192    /// Before  creating  the  link,  the  kernel  checks the nesting of the keyrings
193    /// and returns appropriate errors if the link would produce a cycle or if the
194    /// nesting of keyrings would be too deep (The limit on the nesting of keyrings is
195    /// determined by the kernel constant KEYRING_SEARCH_MAX_DEPTH, defined with the
196    /// value 6, and is necessary to prevent overflows on the kernel stack when
197    /// recursively searching keyrings).
198    ///
199    /// The caller must have link permission on the key being added and write
200    /// permission on the keyring.
201    pub fn link_key(&self, key: Key) -> Result<(), KeyError> {
202        _ = ffi::keyctl!(
203            KeyCtlOperation::Link,
204            key.get_id().as_raw_id() as _,
205            self.id.as_raw_id() as libc::c_ulong
206        )?;
207        Ok(())
208    }
209
210    /// Unlink a key from this keyring.
211    ///
212    /// If the key is not currently linked into the keyring, an error results. If the
213    /// last link to a key is removed, then that key will be scheduled for destruction.
214    ///
215    /// The caller must have write permission on the keyring from which the key is being
216    /// removed.
217    pub fn unlink_key(&self, key: Key) -> Result<(), KeyError> {
218        _ = ffi::keyctl!(
219            KeyCtlOperation::Unlink,
220            key.get_id().as_raw_id() as _,
221            self.id.as_raw_id() as libc::c_ulong
222        )?;
223        Ok(())
224    }
225
226    /// Link another keyring to this keyring.
227    ///
228    /// Behaves similarly to link_key, but links a KeyRing instead. The caller
229    /// must have link permission on the keyring being added as a link, and
230    /// write permission on this keyring.
231    pub fn link_keyring(&self, keyring: KeyRing) -> Result<(), KeyError> {
232        _ = ffi::keyctl!(
233            KeyCtlOperation::Link,
234            keyring.id.as_raw_id() as libc::c_ulong,
235            self.id.as_raw_id() as libc::c_ulong
236        )?;
237        Ok(())
238    }
239
240    /// Unlink another keyring from this keyring.
241    ///
242    /// Behaves similarly to unlink_key, but unlinks a KeyRing instead. The
243    /// caller must have write permission on the keyring to remove links
244    /// from it.
245    pub fn unlink_keyring(&self, keyring: KeyRing) -> Result<(), KeyError> {
246        _ = ffi::keyctl!(
247            KeyCtlOperation::Unlink,
248            keyring.id.as_raw_id() as libc::c_ulong,
249            self.id.as_raw_id() as libc::c_ulong
250        )?;
251        Ok(())
252    }
253
254    /// Link a default keyring from this keyring.
255    ///
256    /// This method does the same thing as link_keyring, but links one of the
257    /// special keyrings defined by the system. This is useful when you
258    /// don't want to have to open a keyring before linking it.
259    ///
260    /// The caller must have link permissions on the added keyring, and write
261    /// permission on this keyring. Requesting to link to a non-existent default
262    /// keyring will result in that keyring being created automatically.
263    pub fn link_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> {
264        _ = ffi::keyctl!(
265            KeyCtlOperation::Link,
266            keyringid as libc::c_ulong,
267            self.id.as_raw_id() as libc::c_ulong
268        )?;
269        Ok(())
270    }
271
272    /// Unlink a default keyring from this keyring.
273    ///
274    /// This method does the same thing as unlink_keyring, but unlinks one of
275    /// the special keyrings defined by the system. This is useful when you
276    /// don't want to have to open a keyring before unlinking it.
277    ///
278    /// The caller must have write permission on this keyring. In addition, this
279    /// method will return KeyError::KeyDoesNotExist if the target keyring has
280    /// not yet been created.
281    pub fn unlink_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> {
282        _ = ffi::keyctl!(
283            KeyCtlOperation::Unlink,
284            keyringid as libc::c_ulong,
285            self.id.as_raw_id() as libc::c_ulong
286        )?;
287        Ok(())
288    }
289
290    /// Clear the contents of (i.e., unlink all keys from) this keyring.
291    ///
292    /// The caller must have write permission on the keyring.
293    pub fn clear(&self) -> Result<(), KeyError> {
294        _ = ffi::keyctl!(KeyCtlOperation::Clear, self.id.as_raw_id() as libc::c_ulong)?;
295        Ok(())
296    }
297}
298
299#[cfg(test)]
300mod test {
301    use super::*;
302    use crate::{KeyPermissionsBuilder, Permission};
303
304    #[test]
305    fn test_from_special_id() {
306        // Test that a keyring that normally doesn't exist by default is
307        // created when called.
308        let ring = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap();
309        assert!(ring.id.as_raw_id() > 0);
310
311        // Test that a keyring that should already exist is returned
312        let ring = KeyRing::from_special_id(KeyRingIdentifier::User, false).unwrap();
313        assert!(ring.id.as_raw_id() > 0);
314    }
315
316    #[test]
317    fn test_get_persistent() {
318        // Test that a keyring that should already exist is returned
319        let user_ring = KeyRing::from_special_id(KeyRingIdentifier::User, false).unwrap();
320        assert!(user_ring.id.as_raw_id() > 0);
321
322        let user_perm_ring = KeyRing::get_persistent(KeyRingIdentifier::User).unwrap();
323        assert_ne!(user_ring, user_perm_ring);
324    }
325
326    #[test]
327    fn test_metadata() {
328        // Test that a keyring that normally doesn't exist by default is
329        // created when called.
330        let ring = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap();
331        assert!(ring.id.as_raw_id() > 0);
332
333        // Obtain and verify the info
334        let info = ring.metadata().unwrap();
335        assert_eq!(info.get_type(), KeyType::KeyRing);
336        assert_eq!(info.get_uid(), unsafe { libc::geteuid() });
337        assert_eq!(info.get_gid(), unsafe { libc::getegid() });
338        assert_eq!(info.get_description(), "_tid");
339    }
340
341    #[test]
342    fn test_search_existing_key() {
343        // Test that a keyring that normally doesn't exist by default is
344        // created when called.
345        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
346        let key = ring.add_key("test_search", b"data").unwrap();
347
348        // Ensure we have search permission on the key
349        let perms = KeyPermissionsBuilder::builder()
350            .posessor(Permission::ALL)
351            .user(Permission::ALL)
352            .build();
353
354        // Enforce perms
355        key.set_perms(perms).unwrap();
356
357        // Search should succeed
358        let result = ring.search("test_search").unwrap();
359
360        // Assert that the ID is the same
361        assert_eq!(key.get_id(), result.get_id());
362
363        // Request should also succeed
364        let result = ring.request_key("test_search", None::<&str>).unwrap();
365
366        // Assert that the ID is the same
367        assert_eq!(key.get_id(), result.get_id());
368
369        // Invalidate the key
370        key.invalidate().unwrap();
371    }
372
373    #[test]
374    fn test_request_non_existing_key() {
375        // Test that a keyring that normally doesn't exist by default is
376        // created when called.
377        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
378
379        let result = ring.request_key("test_request_no_exist", None::<&str>);
380
381        assert!(result.is_err());
382        assert_eq!(result.unwrap_err(), KeyError::KeyDoesNotExist);
383    }
384
385    #[test]
386    #[ignore]
387    fn test_request_non_existing_key_callout() {
388        let callout = "Test Data from Callout";
389
390        // Test that a keyring that normally doesn't exist by default is
391        // created when called.
392        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
393
394        // The test expects that the key is instantiated by a program invoked by
395        // /sbin/request-key and that the key data is taken from the callout info
396        // passed here.
397        //
398        // The following examples/keyctl command in /etc/request-key.conf is known to work:
399        // create	user	test_callout	*		/path/to/examples/keyctl instantiate --keyid %k --payload %c --ring %S
400        let key = ring.request_key("test_callout", Some(callout)).unwrap();
401
402        // Verify the payload
403        let payload = key.read_to_vec().unwrap();
404        assert_eq!(callout.as_bytes(), &payload);
405
406        // Invalidate the key
407        key.invalidate().unwrap();
408    }
409
410    #[test]
411    fn test_search_non_existing_key() {
412        // Test that a keyring that normally doesn't exist by default is
413        // created when called.
414        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
415
416        // Search should succeed
417        let result = ring.search("test_search_no_exist");
418
419        // Assert that the ID is the same
420        assert!(result.is_err());
421        assert_eq!(result.unwrap_err(), KeyError::KeyDoesNotExist);
422    }
423
424    #[test]
425    fn test_link_unlink_keyrings() {
426        // Get a couple of unlinked keyrings to test
427        let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
428        assert!(sess.id.as_raw_id() > 0);
429        let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap();
430        assert!(thread.id.as_raw_id() > 0);
431
432        // Assert that the keyrings are not linked
433        let items = sess.get_links(200).unwrap();
434        assert!(!items.contains(&thread));
435
436        // Link the keyrings
437        let _ = sess.link_keyring(thread).unwrap();
438
439        // Assert that the keyrings are now linked
440        let items = sess.get_links(200).unwrap();
441        assert!(items.contains(&thread));
442
443        // Unlink the keyrings
444        let _ = sess.unlink_keyring(thread).unwrap();
445
446        // Assert that the keyrings are unlinked again
447        let items = sess.get_links(200).unwrap();
448        assert!(!items.contains(&thread));
449    }
450    #[test]
451    fn test_link_unlink_keyrings_with_id() {
452        // Get a couple of unlinked keyrings to test
453        let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
454        assert!(sess.id.as_raw_id() > 0);
455        let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap();
456        assert!(thread.id.as_raw_id() > 0);
457
458        // Assert that the keyrings are not linked
459        let items = sess.get_links(200).unwrap();
460        assert!(!items.contains(&thread));
461
462        // Link the keyrings
463        let _ = sess.link_keyring_id(KeyRingIdentifier::Thread).unwrap();
464
465        // Assert that the keyrings are now linked
466        let items = sess.get_links(200).unwrap();
467        assert!(items.contains(&thread));
468
469        // Unlink the keyrings
470        let _ = sess.unlink_keyring_id(KeyRingIdentifier::Thread).unwrap();
471
472        // Assert that the keyrings are unlinked again
473        let items = sess.get_links(200).unwrap();
474        assert!(!items.contains(&thread));
475    }
476
477    #[test]
478    fn test_linking_nonexistent_keyrings() {
479        // Get existent keyring
480        let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
481        assert!(sess.id.as_raw_id() > 0);
482
483        // Test that the target keyring doesn't exist
484        let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, false);
485        assert!(matches!(thread, Err(KeyError::KeyDoesNotExist)));
486
487        // Unlinking a non-existent keyring
488        let result = sess.unlink_keyring_id(KeyRingIdentifier::Thread);
489        assert!(matches!(result, Err(KeyError::KeyDoesNotExist)));
490
491        // Linking a non-existent keyring
492        sess.link_keyring_id(KeyRingIdentifier::Thread).unwrap();
493
494        // After attempting to link the special keyring, it will have been created
495        let sess = KeyRing::from_special_id(KeyRingIdentifier::Thread, false).unwrap();
496        assert!(sess.id.as_raw_id() > 0);
497    }
498
499    #[test]
500    fn test_get_linked_items() {
501        // Test that a keyring that should already exist is returned
502        let ring = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap();
503        assert!(ring.id.as_raw_id() > 0);
504
505        // Add the key
506        let key = ring.add_key("test_read_key", b"test").unwrap();
507
508        // Obtain a list of the linked keys
509        let items = ring.get_links(200).unwrap();
510
511        // Assert that the key is in the ring
512        assert!(items.len() > 0);
513        assert!(items.contains(&key));
514
515        // Use the alternate reference to the key
516        let key_ref = items.get(&key).unwrap().as_key().unwrap();
517
518        // Invalidate the key
519        key_ref.invalidate().unwrap();
520
521        // Assert that the key is no longer on the ring
522        let items = ring.get_links(200).unwrap();
523        assert!(!items.contains(&key));
524    }
525}