notedeck

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

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 }