enigma-storage 0.0.1

Encrypted local storage for Enigma with mandatory at-rest encryption and cross-platform key vault providers.
Documentation
use std::path::{Path, PathBuf};

use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use blake3::Hasher;
use rand::RngCore;
use security_framework::base::errSecItemNotFound;
use security_framework::passwords::{get_generic_password, set_generic_password};

use crate::error::{EnigmaStorageError, Result};
use crate::key_provider::{KeyProvider, MasterKey};

pub struct MacosKeychainKeyProvider {
    root: PathBuf,
    namespace: String,
}

impl MacosKeychainKeyProvider {
    pub fn new<P: AsRef<Path>>(root: P, namespace: &str) -> Self {
        MacosKeychainKeyProvider {
            root: root.as_ref().to_path_buf(),
            namespace: namespace.to_owned(),
        }
    }

    fn service_name(&self) -> &'static str {
        "enigma-storage"
    }

    fn account_name(&self) -> String {
        let mut hasher = Hasher::new();
        hasher.update(b"enigma:kc:acct:v1");
        hasher.update(self.root.to_string_lossy().as_bytes());
        hasher.update(self.namespace.as_bytes());
        hasher.finalize().to_hex().to_string()
    }

    fn load_existing(&self) -> Result<Option<MasterKey>> {
        match get_generic_password(self.service_name(), &self.account_name()) {
            Ok(bytes) => {
                let decoded = STANDARD
                    .decode(bytes)
                    .map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))?;
                if decoded.len() != 32 {
                    return Err(EnigmaStorageError::InvalidKey);
                }
                let mut key = [0u8; 32];
                key.copy_from_slice(&decoded);
                Ok(Some(MasterKey::new(key)))
            }
            Err(e) => {
                if e.code() == errSecItemNotFound {
                    Ok(None)
                } else {
                    Err(EnigmaStorageError::KeyProviderError(e.to_string()))
                }
            }
        }
    }

    fn store_key(&self, key: &[u8; 32]) -> Result<()> {
        let encoded = STANDARD.encode(key);
        set_generic_password(self.service_name(), &self.account_name(), encoded.as_bytes())
            .map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))
    }
}

impl KeyProvider for MacosKeychainKeyProvider {
    fn get_or_create_master_key(&self) -> Result<MasterKey> {
        if let Some(key) = self.load_existing()? {
            return Ok(key);
        }
        let mut key_bytes = [0u8; 32];
        rand::thread_rng().fill_bytes(&mut key_bytes);
        self.store_key(&key_bytes)?;
        Ok(MasterKey::new(key_bytes))
    }

    fn get_master_key(&self) -> Result<MasterKey> {
        self.load_existing()?
            .ok_or_else(|| EnigmaStorageError::KeyProviderError("key not found in keychain".into()))
    }
}