From a708eadf103274ba1e083fa87c0bbf9a99dfc896 Mon Sep 17 00:00:00 2001 From: Adam Fontenot Date: Thu, 22 Aug 2024 06:47:46 -0400 Subject: [PATCH 1/2] Add methods for linking a keyring to another keyring This is an important keyctl operation that can have implications on whether a key is readable and shows up in Search operations. It may not be immediately be apparent that this is a supported operation when calling KEYCTL_LINK, you have to dig through the docs to find this. man 7 keyrings: > As previously mentioned, keyrings are a special type of key that contains links to other keys (which may include other keyrings). Keys may be linked to by multiple keyrings. --- src/keyring.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/keyring.rs b/src/keyring.rs index 51b1dc7..667c218 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -195,6 +195,71 @@ impl KeyRing { Ok(()) } + /// Link another keyring to this keyring. + /// + /// Behaves similarly to link_key, but links a KeyRing instead. The caller + /// must have link permission on the keyring being added as a link, and + /// write permission on this keyring. + pub fn link_keyring(&self, keyring: KeyRing) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Link, + keyring.id.as_raw_id() as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + + /// Unlink another keyring from this keyring. + /// + /// Behaves similarly to unlink_key, but unlinks a KeyRing instead. The + /// caller must have write permission on the keyring to remove links + /// from it. + pub fn unlink_keyring(&self, keyring: KeyRing) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Unlink, + keyring.id.as_raw_id() as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + + /// Link a default keyring from this keyring. + /// + /// This method does the same thing as link_keyring, but links one of the + /// special keyrings defined by the system. This is useful when you + /// don't want to have to open a keyring before linking it. + /// + /// The caller must have link permissions on the added keyring, and write + /// permission on this keyring. In addition, this method will return + /// KeyError::KeyDoesNotExist if the target keyring has not yet been + /// created. + pub fn link_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Link, + keyringid as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + + /// Unlink a default keyring from this keyring. + /// + /// This method does the same thing as unlink_keyring, but unlinks one of + /// the special keyrings defined by the system. This is useful when you + /// don't want to have to open a keyring before unlinking it. + /// + /// The caller must have write permission on this keyring. In addition, this + /// method will return KeyError::KeyDoesNotExist if the target keyring has + /// not yet been created. + pub fn unlink_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Unlink, + keyringid as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + /// Clear the contents of (i.e., unlink all keys from) this keyring. /// /// The caller must have write permission on the keyring. From ec82d7b30afa47ef903eaaf4073009bb8bd9b1fd Mon Sep 17 00:00:00 2001 From: Adam Fontenot Date: Wed, 28 Aug 2024 02:26:36 -0400 Subject: [PATCH 2/2] Add tests for keyring linking functions Also fixes a minor doc issue in previous commit --- src/keyring.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/src/keyring.rs b/src/keyring.rs index 667c218..49e6adf 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -230,9 +230,8 @@ impl KeyRing { /// don't want to have to open a keyring before linking it. /// /// The caller must have link permissions on the added keyring, and write - /// permission on this keyring. In addition, this method will return - /// KeyError::KeyDoesNotExist if the target keyring has not yet been - /// created. + /// permission on this keyring. Requesting to link to a non-existent default + /// keyring will result in that keyring being created automatically. pub fn link_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> { _ = ffi::keyctl!( KeyCtlOperation::Link, @@ -351,6 +350,81 @@ mod test { assert_eq!(result.unwrap_err(), KeyError::KeyDoesNotExist); } + #[test] + fn test_link_unlink_keyrings() { + // Get a couple of unlinked keyrings to test + let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap(); + assert!(thread.id.as_raw_id() > 0); + + // Assert that the keyrings are not linked + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + + // Link the keyrings + let _ = sess.link_keyring(thread).unwrap(); + + // Assert that the keyrings are now linked + let items = sess.get_links(200).unwrap(); + assert!(items.contains(&thread)); + + // Unlink the keyrings + let _ = sess.unlink_keyring(thread).unwrap(); + + // Assert that the keyrings are unlinked again + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + } + #[test] + fn test_link_unlink_keyrings_with_id() { + // Get a couple of unlinked keyrings to test + let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap(); + assert!(thread.id.as_raw_id() > 0); + + // Assert that the keyrings are not linked + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + + // Link the keyrings + let _ = sess.link_keyring_id(KeyRingIdentifier::Thread).unwrap(); + + // Assert that the keyrings are now linked + let items = sess.get_links(200).unwrap(); + assert!(items.contains(&thread)); + + // Unlink the keyrings + let _ = sess.unlink_keyring_id(KeyRingIdentifier::Thread).unwrap(); + + // Assert that the keyrings are unlinked again + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + } + + #[test] + fn test_linking_nonexistent_keyrings() { + // Get existent keyring + let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + + // Test that the target keyring doesn't exist + let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, false); + assert!(matches!(thread, Err(KeyError::KeyDoesNotExist))); + + // Unlinking a non-existent keyring + let result = sess.unlink_keyring_id(KeyRingIdentifier::Thread); + assert!(matches!(result, Err(KeyError::KeyDoesNotExist))); + + // Linking a non-existent keyring + sess.link_keyring_id(KeyRingIdentifier::Thread).unwrap(); + + // After attempting to link the special keyring, it will have been created + let sess = KeyRing::from_special_id(KeyRingIdentifier::Thread, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + } + #[test] fn test_get_linked_items() { // Test that a keyring that should already exist is returned