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()))
}
}