security_framework_key_storage.rs (6111B)
1 use std::borrow::Cow; 2 3 use enostr::{Keypair, Pubkey, SecretKey}; 4 use security_framework::{ 5 item::{ItemClass, ItemSearchOptions, Limit, SearchResult}, 6 passwords::{delete_generic_password, set_generic_password}, 7 }; 8 use tracing::error; 9 10 use crate::Error; 11 12 use super::{key_storage_impl::KeyStorageError, KeyStorageResponse}; 13 14 #[derive(Debug, PartialEq)] 15 pub struct SecurityFrameworkKeyStorage { 16 pub service_name: Cow<'static, str>, 17 } 18 19 impl SecurityFrameworkKeyStorage { 20 pub fn new(service_name: String) -> Self { 21 SecurityFrameworkKeyStorage { 22 service_name: Cow::Owned(service_name), 23 } 24 } 25 26 fn add_key_internal(&self, key: &Keypair) -> Result<(), KeyStorageError> { 27 match set_generic_password( 28 &self.service_name, 29 key.pubkey.hex().as_str(), 30 key.secret_key 31 .as_ref() 32 .map_or_else(|| &[] as &[u8], |sc| sc.as_secret_bytes()), 33 ) { 34 Ok(_) => Ok(()), 35 Err(e) => Err(KeyStorageError::Addition(Error::Generic(e.to_string()))), 36 } 37 } 38 39 fn get_pubkey_strings(&self) -> Vec<String> { 40 let search_results = ItemSearchOptions::new() 41 .class(ItemClass::generic_password()) 42 .service(&self.service_name) 43 .load_attributes(true) 44 .limit(Limit::All) 45 .search(); 46 47 let mut accounts = Vec::new(); 48 49 if let Ok(search_results) = search_results { 50 for result in search_results { 51 if let Some(map) = result.simplify_dict() { 52 if let Some(val) = map.get("acct") { 53 accounts.push(val.clone()); 54 } 55 } 56 } 57 } 58 59 accounts 60 } 61 62 fn get_pubkeys(&self) -> Vec<Pubkey> { 63 self.get_pubkey_strings() 64 .iter_mut() 65 .filter_map(|pubkey_str| Pubkey::from_hex(pubkey_str.as_str()).ok()) 66 .collect() 67 } 68 69 fn get_privkey_bytes_for(&self, account: &str) -> Option<Vec<u8>> { 70 let search_result = ItemSearchOptions::new() 71 .class(ItemClass::generic_password()) 72 .service(&self.service_name) 73 .load_data(true) 74 .account(account) 75 .search(); 76 77 if let Ok(results) = search_result { 78 if let Some(SearchResult::Data(vec)) = results.first() { 79 return Some(vec.clone()); 80 } 81 } 82 83 None 84 } 85 86 fn get_secret_key_for_pubkey(&self, pubkey: &Pubkey) -> Option<SecretKey> { 87 if let Some(bytes) = self.get_privkey_bytes_for(pubkey.hex().as_str()) { 88 SecretKey::from_slice(bytes.as_slice()).ok() 89 } else { 90 None 91 } 92 } 93 94 fn get_all_keypairs(&self) -> Vec<Keypair> { 95 self.get_pubkeys() 96 .iter() 97 .map(|pubkey| { 98 let maybe_secret = self.get_secret_key_for_pubkey(pubkey); 99 Keypair::new(*pubkey, maybe_secret) 100 }) 101 .collect() 102 } 103 104 fn delete_key(&self, pubkey: &Pubkey) -> Result<(), KeyStorageError> { 105 match delete_generic_password(&self.service_name, pubkey.hex().as_str()) { 106 Ok(_) => Ok(()), 107 Err(e) => { 108 error!("delete key error {}", e); 109 Err(KeyStorageError::Removal(Error::Generic(e.to_string()))) 110 } 111 } 112 } 113 } 114 115 impl SecurityFrameworkKeyStorage { 116 pub fn add_key(&self, key: &Keypair) -> KeyStorageResponse<()> { 117 KeyStorageResponse::ReceivedResult(self.add_key_internal(key)) 118 } 119 120 pub fn get_keys(&self) -> KeyStorageResponse<Vec<Keypair>> { 121 KeyStorageResponse::ReceivedResult(Ok(self.get_all_keypairs())) 122 } 123 124 pub fn remove_key(&self, key: &Keypair) -> KeyStorageResponse<()> { 125 KeyStorageResponse::ReceivedResult(self.delete_key(&key.pubkey)) 126 } 127 } 128 129 #[cfg(test)] 130 mod tests { 131 use super::*; 132 use enostr::FullKeypair; 133 134 static TEST_SERVICE_NAME: &str = "NOTEDECKTEST"; 135 static STORAGE: SecurityFrameworkKeyStorage = SecurityFrameworkKeyStorage { 136 service_name: Cow::Borrowed(TEST_SERVICE_NAME), 137 }; 138 139 // individual tests are ignored so test runner doesn't run them all concurrently 140 // TODO: a way to run them all serially should be devised 141 142 #[test] 143 #[ignore] 144 fn add_and_remove_test_pubkey_only() { 145 let num_keys_before_test = STORAGE.get_pubkeys().len(); 146 147 let keypair = FullKeypair::generate().to_keypair(); 148 let add_result = STORAGE.add_key_internal(&keypair); 149 assert!(add_result.is_ok()); 150 151 let get_pubkeys_result = STORAGE.get_pubkeys(); 152 assert_eq!(get_pubkeys_result.len() - num_keys_before_test, 1); 153 154 let remove_result = STORAGE.delete_key(&keypair.pubkey); 155 assert!(remove_result.is_ok()); 156 157 let keys = STORAGE.get_pubkeys(); 158 assert_eq!(keys.len() - num_keys_before_test, 0); 159 } 160 161 fn add_and_remove_full_n(n: usize) { 162 let num_keys_before_test = STORAGE.get_all_keypairs().len(); 163 // there must be zero keys in storage for the test to work as intended 164 assert_eq!(num_keys_before_test, 0); 165 166 let expected_keypairs: Vec<Keypair> = (0..n) 167 .map(|_| FullKeypair::generate().to_keypair()) 168 .collect(); 169 170 expected_keypairs.iter().for_each(|keypair| { 171 let add_result = STORAGE.add_key_internal(keypair); 172 assert!(add_result.is_ok()); 173 }); 174 175 let asserted_keypairs = STORAGE.get_all_keypairs(); 176 assert_eq!(expected_keypairs, asserted_keypairs); 177 178 expected_keypairs.iter().for_each(|keypair| { 179 let remove_result = STORAGE.delete_key(&keypair.pubkey); 180 assert!(remove_result.is_ok()); 181 }); 182 183 let num_keys_after_test = STORAGE.get_all_keypairs().len(); 184 assert_eq!(num_keys_after_test, 0); 185 } 186 187 #[test] 188 #[ignore] 189 fn add_and_remove_full() { 190 add_and_remove_full_n(1); 191 } 192 193 #[test] 194 #[ignore] 195 fn add_and_remove_full_10() { 196 add_and_remove_full_n(10); 197 } 198 }