Skip to main content

keyring_search/
keyutils.rs

1use std::collections::HashMap;
2
3use super::error::Error as ErrorCode;
4use super::search::{CredentialSearch, CredentialSearchApi, CredentialSearchResult};
5use linux_keyutils::{KeyRing, KeyRingIdentifier, KeyType, Permission};
6
7pub struct KeyutilsCredentialSearch {}
8
9/// Returns the Secret service default credential search structure.
10///
11/// This creates a new search structure. The by method has concrete types to search by,
12/// each corresponding to the different keyrings found within the kernel keyctl.
13pub fn default_credential_search() -> Box<CredentialSearch> {
14    Box::new(KeyutilsCredentialSearch {})
15}
16
17impl CredentialSearchApi for KeyutilsCredentialSearch {
18    /// The default search for keyutils is in the 'session' keyring.
19    ///
20    /// If more control over the keyring is needed, call the
21    /// (search_by_keyring) function manually.
22    fn by(&self, _by: &str, query: &str) -> CredentialSearchResult {
23        search_by_keyring("session", query)
24    }
25}
26/// Search for credential items in the specified keyring.
27///
28/// To utilize search of any keyring, call this function
29/// directly. The generic platform independent search
30/// defaults to the `session` keyring.
31pub fn search_by_keyring(by: &str, query: &str) -> CredentialSearchResult {
32    let by = match by {
33        "thread" => KeyRingIdentifier::Thread,
34        "process" => KeyRingIdentifier::Process,
35        "session" => KeyRingIdentifier::Session,
36        "user" => KeyRingIdentifier::User,
37        "user session" => KeyRingIdentifier::UserSession,
38        "group" => KeyRingIdentifier::Group,
39        _ => return Err(ErrorCode::SearchError("must match keyutils keyring identifiers: thread, process, session, user, user session, group".to_string())),
40    };
41
42    let ring = match KeyRing::from_special_id(by, false) {
43        Ok(ring) => ring,
44        Err(err) => return Err(ErrorCode::SearchError(err.to_string())),
45    };
46
47    let result = match ring.search(query) {
48        Ok(result) => result,
49        Err(err) => match err {
50            linux_keyutils::KeyError::KeyDoesNotExist => return Err(ErrorCode::NoResults),
51            _ => return Err(ErrorCode::SearchError(err.to_string())),
52        },
53    };
54
55    let result_data = match result.metadata() {
56        Ok(data) => data,
57        Err(err) => return Err(ErrorCode::SearchError(err.to_string())),
58    };
59
60    let key_type = get_key_type(result_data.get_type());
61
62    let permission_bits = result_data.get_perms().bits().to_be_bytes();
63
64    let permission_string = get_permission_chars(permission_bits[0]);
65
66    let mut outer_map: HashMap<String, HashMap<String, String>> = HashMap::new();
67    let mut inner_map: HashMap<String, String> = HashMap::new();
68
69    inner_map.insert("perm".to_string(), permission_string);
70    inner_map.insert("gid".to_string(), result_data.get_gid().to_string());
71    inner_map.insert("uid".to_string(), result_data.get_uid().to_string());
72    inner_map.insert("ktype".to_string(), key_type);
73    inner_map.insert(
74        "description".to_string(),
75        result_data.get_description().to_string(),
76    );
77
78    outer_map.insert(result.get_id().0.to_string(), inner_map);
79
80    Ok(outer_map)
81}
82fn get_key_type(key_type: KeyType) -> String {
83    match key_type {
84        KeyType::KeyRing => "KeyRing".to_string(),
85        KeyType::BigKey => "BigKey".to_string(),
86        KeyType::Logon => "Logon".to_string(),
87        KeyType::User => "User".to_string(),
88    }
89}
90// Converts permission bits to their corresponding permission characters to match keyctl command in terminal.
91fn get_permission_chars(permission_data: u8) -> String {
92    let perm_types = [
93        Permission::VIEW.bits(),
94        Permission::READ.bits(),
95        Permission::WRITE.bits(),
96        Permission::SEARCH.bits(),
97        Permission::LINK.bits(),
98        Permission::SETATTR.bits(),
99        Permission::ALL.bits(),
100    ];
101
102    let perm_chars = ['v', 'r', 'w', 's', 'l', 'a', '-'];
103
104    let mut perm_string = String::new();
105    perm_string.push('-');
106
107    for i in (0..perm_types.len()).rev() {
108        if permission_data & perm_types[i] != 0 {
109            perm_string.push(perm_chars[i]);
110        } else {
111            perm_string.push('-');
112        }
113    }
114
115    perm_string
116}
117
118#[cfg(test)]
119mod tests {
120    use super::{get_key_type, get_permission_chars, KeyRing, KeyRingIdentifier};
121    use crate::{tests::generate_random_string, Error, Limit, List, Search};
122    use keyring::{credential::CredentialApi, keyutils::KeyutilsCredential};
123    use std::collections::HashSet;
124
125    #[test]
126    fn test_search() {
127        let name = generate_random_string();
128        let entry = keyring::keyutils::KeyutilsCredential::new_with_target(None, &name, &name)
129            .expect("Failed to create searchable entry");
130        let password = "search test password";
131        entry
132            .set_password(password)
133            .expect("Failed to set password");
134
135        let actual: &KeyutilsCredential =
136            &entry.get_credential().expect("Not a keyutils credential 1");
137
138        let keyring = KeyRing::from_special_id(KeyRingIdentifier::Session, false)
139            .expect("No session keyring");
140        let credential = keyring
141            .search(&actual.description)
142            .expect("Failed to downcast to linux-keyutils type");
143        let metadata = credential
144            .metadata()
145            .expect("Failed to get credential metadata");
146
147        let mut expected = format!("{}\n", credential.get_id().0,);
148        expected.push_str(format!("gid: {}\n", metadata.get_gid()).as_str());
149        expected.push_str(format!("uid: {}\n", metadata.get_uid()).as_str());
150        expected.push_str(format!("description: {}\n", actual.description).as_str());
151        expected.push_str(
152            format!(
153                "perm: {}\n",
154                get_permission_chars(metadata.get_perms().bits().to_be_bytes()[0])
155            )
156            .as_str(),
157        );
158        expected.push_str(format!("ktype: {}\n", get_key_type(metadata.get_type())).as_str());
159
160        let query = format!("keyring-rs:{}@{}", name, name);
161        let result = Search {
162            inner: Box::new(super::KeyutilsCredentialSearch {}),
163        }
164        .by_user(&query);
165        let list = List::list_credentials(&result, Limit::All);
166
167        let expected_set: HashSet<&str> = expected.lines().collect();
168        let result_set: HashSet<&str> = list.lines().collect();
169        assert_eq!(expected_set, result_set, "Search results do not match");
170        entry
171            .delete_password()
172            .expect("Couldn't delete test-search-by-user");
173    }
174
175    #[test]
176    fn test_no_results() {
177        let name = generate_random_string();
178        let search = Search::new()
179            .expect("Error creating new search")
180            .by_user(&name);
181
182        assert!(matches!(search.unwrap_err(), Error::NoResults));
183    }
184}