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