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}