notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }